diff options
author | Jo Shields <directhex@apebox.org> | 2014-02-19 22:12:43 +0000 |
---|---|---|
committer | Jo Shields <directhex@apebox.org> | 2014-02-19 22:12:43 +0000 |
commit | 9972bf87b4f27d9c8f358ef8414ac1ab957a2f0f (patch) | |
tree | 5bb230c1d698659115f918e243c1d4b0aa4c7f51 /mcs/class/Microsoft.Build | |
parent | d0a215f5626219ff7927f576588a777e5331c7be (diff) | |
download | mono-upstream/3.2.8+dfsg.tar.gz |
Imported Upstream version 3.2.8+dfsgupstream/3.2.8+dfsg
Diffstat (limited to 'mcs/class/Microsoft.Build')
103 files changed, 9505 insertions, 1344 deletions
diff --git a/mcs/class/Microsoft.Build/Assembly/AssemblyInfo.cs b/mcs/class/Microsoft.Build/Assembly/AssemblyInfo.cs index edc155a8af..e14ad00346 100644 --- a/mcs/class/Microsoft.Build/Assembly/AssemblyInfo.cs +++ b/mcs/class/Microsoft.Build/Assembly/AssemblyInfo.cs @@ -46,9 +46,9 @@ using System.Runtime.Versioning; [assembly: AssemblyCompany (Consts.MonoCompany)] [assembly: AssemblyProduct (Consts.MonoProduct)] [assembly: AssemblyCopyright (Consts.MonoCopyright)] -[assembly: AssemblyVersion (Consts.FxVersion)] -[assembly: SatelliteContractVersion (Consts.FxVersion)] -[assembly: AssemblyInformationalVersion (Consts.FxFileVersion)] +[assembly: AssemblyVersion (XBuildConsts.AssemblyVersion)] +[assembly: SatelliteContractVersion (XBuildConsts.AssemblyVersion)] +[assembly: AssemblyInformationalVersion (XBuildConsts.FileVersion)] [assembly: NeutralResourcesLanguage ("en-US")] @@ -57,6 +57,6 @@ using System.Runtime.Versioning; [assembly: AssemblyDelaySign (true)] [assembly: AssemblyKeyFile("../msfinal.pub")] -[assembly: AssemblyFileVersion (Consts.FxFileVersion)] +[assembly: AssemblyFileVersion (XBuildConsts.FileVersion)] [assembly: CompilationRelaxations (CompilationRelaxations.NoStringInterning)] diff --git a/mcs/class/Microsoft.Build/Makefile b/mcs/class/Microsoft.Build/Makefile index 173638c983..931b9fc7e5 100644 --- a/mcs/class/Microsoft.Build/Makefile +++ b/mcs/class/Microsoft.Build/Makefile @@ -2,14 +2,10 @@ thisdir = class/Microsoft.Build SUBDIRS = include ../../build/rules.make -LIBRARY = Microsoft.Build.dll +XBUILD_DIR=$(topdir)/tools/xbuild +include $(XBUILD_DIR)/xbuild.make -ifneq (4, $(FRAMEWORK_VERSION_MAJOR)) -LIBRARY_NAME = dummy-Microsoft.Build.dll -NO_INSTALL = yes -NO_TEST = yes -NO_SIGN_ASSEMBLY = yes -endif +LIBRARY = Microsoft.Build.dll LIB_MCS_FLAGS = \ /r:$(corlib) \ @@ -17,18 +13,26 @@ LIB_MCS_FLAGS = \ /r:System.Core.dll \ /r:System.Xml.dll \ /r:Microsoft.Build.Engine.dll \ - /r:Microsoft.Build.Framework.dll + /r:Microsoft.Build.Framework.dll \ + /d:MICROSOFT_BUILD_DLL -TEST_MCS_FLAGS = /r:System.Core.dll +TEST_MCS_FLAGS = $(LIB_MCS_FLAGS) EXTRA_DISTFILES = \ + Microsoft.Build.Internal/ExpressionParser.jay \ Test/FunctionalTestReferenceProject.csproj \ Test/FunctionalTestReferenceProject3.csproj \ Test/Microsoft.Build.Test.csproj \ Test/Microsoft.Build.csproj +EXPR_PARSER = Microsoft.Build.Internal/ExpressionParser + +$(EXPR_PARSER).cs: $(EXPR_PARSER).jay $(topdir)/jay/skeleton.cs + (cd Microsoft.Build.Internal; $(topdir)/../jay/jay -ctv < $(topdir)/../jay/skeleton.cs ExpressionParser.jay > ExpressionParser.cs) + +BUILT_SOURCES = $(EXPR_PARSER).cs + include ../../build/library.make -export TESTING_MONO=a -XBUILD_DIR=../../tools/xbuild -include $(XBUILD_DIR)/xbuild_targets.make +XBUILD_FRAMEWORK_FOLDERS_PATH=xbuild-testing +include $(XBUILD_DIR)/xbuild_test.make diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ElementLocation.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ElementLocation.cs index 43f20dad9b..15fe431cec 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ElementLocation.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ElementLocation.cs @@ -1,10 +1,10 @@ // -// ProjectItemDefinitionInstance.cs +// ElementLocation.cs // // Author: -// Atsushi Enomoto (atsushi@veritas-vos-liberabit.com) +// Atsushi Enomoto (atsushi@xamarin.com) // -// Copyright (C) 2012 Xamarin Inc. +// Copyright (C) 2013 Xamarin Inc. // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the @@ -26,7 +26,6 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if NET_4_5 using Microsoft.Build.Framework; using System; @@ -35,7 +34,10 @@ using System.Collections.Generic; namespace Microsoft.Build.Construction { [Serializable] - public abstract class ElementLocation +#if NET_4_5 + public +#endif + abstract class ElementLocation { public abstract int Column { get; } public abstract string File { get; } @@ -44,7 +46,16 @@ namespace Microsoft.Build.Construction public string LocationString { get { return Line == 0 ? File : String.Format ("{0} ({1}{2})", File, Line, Column != 0 ? "," + Column : String.Empty); } } + + public override bool Equals (object other) + { + var o = other as ElementLocation; + return (object) o != null && o.File == File && o.Line == Line && o.Column == Column; + } + + public override int GetHashCode () + { + return (File.GetHashCode () << 16) + (Line << 8) + Column; + } } } - -#endif diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectChooseElement.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectChooseElement.cs index 551a67b4c5..d5a52d9b78 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectChooseElement.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectChooseElement.cs @@ -29,6 +29,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Xml; using Microsoft.Build.Exceptions; using Microsoft.Build.Internal; @@ -54,8 +55,9 @@ namespace Microsoft.Build.Construction internal override string XmlName { get { return "Choose"; } } - internal override ProjectElement LoadChildElement (string name) + internal override ProjectElement LoadChildElement (XmlReader reader) { + var name = reader.LocalName; switch (name) { case "Otherwise": var other = ContainingProject.CreateOtherwiseElement (); diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectCommentElement.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectCommentElement.cs index 23666183d1..36dc17a4fb 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectCommentElement.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectCommentElement.cs @@ -47,6 +47,7 @@ namespace Microsoft.Build.Construction internal override void Load (XmlReader reader) { + FillLocation (reader); LoadValue (reader); } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectElement.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectElement.cs index 7454665b7e..6a3cd8f2f3 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectElement.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectElement.cs @@ -35,6 +35,8 @@ namespace Microsoft.Build.Construction { public abstract class ProjectElement { + internal const string MSBuildNamespace = "http://schemas.microsoft.com/developer/msbuild/2003"; + internal ProjectElement () { linkedListNode = new LinkedListNode<ProjectElement> (this); @@ -67,9 +69,11 @@ namespace Microsoft.Build.Construction internal virtual void Load (XmlReader reader) { reader.ReadToFollowing (XmlName); + FillLocation (reader); while (reader.MoveToNextAttribute ()) { LoadAttribute (reader.Name, reader.Value); } + reader.MoveToElement (); LoadValue (reader); } internal virtual void LoadAttribute (string name, string value) @@ -84,7 +88,7 @@ namespace Microsoft.Build.Construction Condition = value; break; default: - throw new InvalidProjectFileException (string.Format ( + throw new InvalidProjectFileException (Location, null, string.Format ( "Attribute \"{0}\" is not known on node \"{1}\" [type {2}].", name, XmlName, GetType ())); } @@ -109,5 +113,52 @@ namespace Microsoft.Build.Construction if (!string.IsNullOrWhiteSpace (attributeValue)) writer.WriteAttributeString (attributeName, attributeValue); } + +#if NET_4_5 + public ElementLocation Location { get; private set; } + public ElementLocation LabelLocation { get; private set; } + public ElementLocation ConditionLocation { get; private set; } +#else + internal ElementLocation Location { get; private set; } + internal ElementLocation LabelLocation { get; private set; } + internal ElementLocation ConditionLocation { get; private set; } +#endif + + internal void FillLocation (XmlReader reader) + { + var l = reader as IXmlLineInfo; + if (l != null && l.HasLineInfo ()) + Location = new ProjectElementLocation (reader.BaseURI, l); + if (reader.MoveToAttribute ("Condition") && l.HasLineInfo ()) + ConditionLocation = new ProjectElementLocation (reader.BaseURI, l); + if (reader.MoveToAttribute ("Label") && l.HasLineInfo ()) + LabelLocation = new ProjectElementLocation (reader.BaseURI, l); + reader.MoveToElement (); + } + + class ProjectElementLocation : ElementLocation + { + public ProjectElementLocation (string file, IXmlLineInfo li) + { + this.file = file; + this.line = li.LineNumber; + this.column = li.LinePosition; + } + + string file; + int line; + int column; + + public override string File { get { return file; } } + public override int Line { get { return line; } } + public override int Column { get { return column; } } + } + + internal InvalidProjectFileException CreateError (XmlReader reader, string message, int columnOffset = 0) + { + var li = reader as IXmlLineInfo; + bool valid = li != null && li.HasLineInfo (); + throw new InvalidProjectFileException (reader.BaseURI, valid ? li.LineNumber : 0, valid ? li.LinePosition + columnOffset : 0, 0, 0, message, null, null, null); + } } } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectElementContainer.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectElementContainer.cs index eb9f59e875..1f1619bbd5 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectElementContainer.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectElementContainer.cs @@ -29,6 +29,7 @@ using System.Collections.Generic; using System.Linq; using System.Xml; +using Microsoft.Build.Exceptions; using Microsoft.Build.Internal; namespace Microsoft.Build.Construction @@ -123,11 +124,25 @@ namespace Microsoft.Build.Construction child.Save (writer); } + internal override void Load (XmlReader reader) + { + reader.Read (); + reader.MoveToContent (); + FillLocation (reader); + if (reader.LocalName != XmlName || reader.NamespaceURI != MSBuildNamespace) + throw CreateError (reader, string.Format ("Unexpected XML {0} \"{1}\" in namespace \"{2}\" appeared, while \"{3}\" in namespace \"{4}\" is expected.", + reader.NodeType, reader.LocalName, reader.NamespaceURI, XmlName, MSBuildNamespace), -1); + while (reader.MoveToNextAttribute ()) { + LoadAttribute (reader.Name, reader.Value); + } + LoadValue (reader); + } + internal override void LoadValue (XmlReader reader) { while (reader.Read ()) { if (reader.NodeType == XmlNodeType.Element) { - var child = LoadChildElement (reader.Name); + var child = LoadChildElement (reader); child.Load (reader.ReadSubtree ()); } else if (reader.NodeType == XmlNodeType.Comment) { var commentElement = new ProjectCommentElement (ContainingProject); @@ -137,6 +152,6 @@ namespace Microsoft.Build.Construction } } - internal abstract ProjectElement LoadChildElement (string name); + internal abstract ProjectElement LoadChildElement (XmlReader reader); } } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectExtensionsElement.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectExtensionsElement.cs index d6bcbbb12b..48d35f4f0f 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectExtensionsElement.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectExtensionsElement.cs @@ -71,6 +71,7 @@ namespace Microsoft.Build.Construction { while (reader.Read () && reader.NodeType != XmlNodeType.Element) ; + FillLocation (reader); using (XmlReader subReader = reader.ReadSubtree ()) { document = new XmlDocument (); document.Load (subReader); diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectImportElement.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectImportElement.cs index d824cc7ee3..8895348390 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectImportElement.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectImportElement.cs @@ -28,6 +28,9 @@ using System; using System.Xml; +using Microsoft.Build.Exceptions; + + namespace Microsoft.Build.Construction { [System.Diagnostics.DebuggerDisplayAttribute ("Project={Project} Condition={Condition}")] @@ -56,6 +59,13 @@ namespace Microsoft.Build.Construction SaveAttribute (writer, "Project", Project); base.SaveValue (writer); } + + internal override void LoadValue (XmlReader reader) + { + if (string.IsNullOrWhiteSpace (Project)) + throw new InvalidProjectFileException (Location, null, "Project attribute is null or empty on an Import element"); + base.LoadValue (reader); + } internal override void LoadAttribute (string name, string value) { diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectImportGroupElement.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectImportGroupElement.cs index 9223b5dc4b..bd346f772a 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectImportGroupElement.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectImportGroupElement.cs @@ -29,6 +29,7 @@ using System; using System.Collections.Generic; using Microsoft.Build.Internal; +using System.Xml; namespace Microsoft.Build.Construction { @@ -50,7 +51,7 @@ namespace Microsoft.Build.Construction return import; } internal override string XmlName { get { return "ImportGroup"; } } - internal override ProjectElement LoadChildElement (string name) + internal override ProjectElement LoadChildElement (XmlReader reader) { return AddImport (null); } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectItemDefinitionElement.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectItemDefinitionElement.cs index 283e8b700c..497f631a07 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectItemDefinitionElement.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectItemDefinitionElement.cs @@ -29,6 +29,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Xml; using Microsoft.Build.Internal; namespace Microsoft.Build.Construction @@ -56,9 +57,9 @@ namespace Microsoft.Build.Construction internal override string XmlName { get { return ItemType; } } - internal override ProjectElement LoadChildElement (string name) + internal override ProjectElement LoadChildElement (XmlReader reader) { - return AddMetadata (name, null); + return AddMetadata (reader.LocalName, null); } } } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectItemDefinitionGroupElement.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectItemDefinitionGroupElement.cs index 43570e828e..1d74932b2c 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectItemDefinitionGroupElement.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectItemDefinitionGroupElement.cs @@ -29,6 +29,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Xml; using Microsoft.Build.Internal; namespace Microsoft.Build.Construction @@ -53,9 +54,9 @@ namespace Microsoft.Build.Construction internal override string XmlName { get { return "ItemDefinitionGroup"; } } - internal override ProjectElement LoadChildElement (string name) + internal override ProjectElement LoadChildElement (XmlReader reader) { - return AddItemDefinition (name); + return AddItemDefinition (reader.LocalName); } } } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectItemElement.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectItemElement.cs index 02a1a6ec49..5b13e20240 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectItemElement.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectItemElement.cs @@ -31,6 +31,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Build.Internal; using System.Xml; +using Microsoft.Build.Exceptions; namespace Microsoft.Build.Construction { @@ -61,6 +62,14 @@ namespace Microsoft.Build.Construction } string @remove; public string Remove { get { return @remove ?? String.Empty; } set { @remove = value; } } + #if NET_4_5 + string keepDuplicates; + public string KeepDuplicates { get { return keepDuplicates ?? String.Empty; } set { keepDuplicates = value; } } + string keepMetadata; + public string KeepMetadata { get { return keepMetadata ?? String.Empty; } set { keepMetadata = value; } } + string removeMetadata; + public string RemoveMetadata { get { return removeMetadata ?? String.Empty; } set { removeMetadata = value; } } + #endif public ProjectMetadataElement AddMetadata (string name, string unevaluatedValue) { var metadata = ContainingProject.CreateMetadataElement (name, unevaluatedValue); @@ -74,9 +83,15 @@ namespace Microsoft.Build.Construction { SaveAttribute (writer, "Include", Include); SaveAttribute (writer, "Exclude", Exclude); +#if NET_4_5 + SaveAttribute (writer, "KeepDuplicates", KeepDuplicates); + SaveAttribute (writer, "KeepMetadata", KeepMetadata); + SaveAttribute (writer, "RemoveMetadata", RemoveMetadata); +#endif SaveAttribute (writer, "Remove", Remove); base.SaveValue (writer); } + internal override void LoadAttribute (string name, string value) { switch (name) { @@ -86,6 +101,17 @@ namespace Microsoft.Build.Construction case "Exclude": Exclude = value; break; +#if NET_4_5 + case "KeepDuplicates": + KeepDuplicates = value; + break; + case "KeepMetadata": + KeepMetadata = value; + break; + case "RemoveMetadata": + RemoveMetadata = value; + break; +#endif case "Remove": Remove = value; break; @@ -94,11 +120,30 @@ namespace Microsoft.Build.Construction break; } } - internal override ProjectElement LoadChildElement (string name) + internal override void LoadValue (XmlReader reader) + { + if (string.IsNullOrWhiteSpace (Include) && string.IsNullOrEmpty (Remove)) + throw new InvalidProjectFileException (Location, null, string.Format ("Both Include and Remove attribute are null or empty on '{0}' item", ItemType)); + base.LoadValue (reader); + } + internal override ProjectElement LoadChildElement (XmlReader reader) { - var metadata = ContainingProject.CreateMetadataElement (name); + var metadata = ContainingProject.CreateMetadataElement (reader.LocalName); AppendChild (metadata); return metadata; } +#if NET_4_5 + public ElementLocation ExcludeLocation { get; private set; } + public ElementLocation IncludeLocation { get; private set; } + public ElementLocation KeepDuplicatesLocation { get; private set; } + public ElementLocation RemoveLocation { get; private set; } + public ElementLocation RemoveMetadataLocation { get; private set; } +#else + ElementLocation ExcludeLocation { get; set; } + ElementLocation IncludeLocation { get; set; } + ElementLocation KeepDuplicatesLocation { get; set; } + ElementLocation RemoveLocation { get; set; } + ElementLocation RemoveMetadataLocation { get; set; } +#endif } } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectItemGroupElement.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectItemGroupElement.cs index 5cece6419c..f0bce4361b 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectItemGroupElement.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectItemGroupElement.cs @@ -26,10 +26,11 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // +using System; using System.Collections.Generic; using System.Linq; +using System.Xml; using Microsoft.Build.Internal; -using System; namespace Microsoft.Build.Construction { @@ -84,9 +85,9 @@ namespace Microsoft.Build.Construction get { return "ItemGroup"; } } - internal override ProjectElement LoadChildElement (string name) + internal override ProjectElement LoadChildElement (XmlReader reader) { - var item = ContainingProject.CreateItemElement (name); + var item = ContainingProject.CreateItemElement (reader.LocalName); AppendChild (item); return item; } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectOnErrorElement.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectOnErrorElement.cs index 5d6b75a59f..1590189e75 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectOnErrorElement.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectOnErrorElement.cs @@ -61,5 +61,10 @@ namespace Microsoft.Build.Construction break; } } + + #if NET_4_5 + public + #endif + ElementLocation ExecuteTargetsAttributeLocation { get; set; } } } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectOtherwiseElement.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectOtherwiseElement.cs index 9c25e50658..b8a2d6bec0 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectOtherwiseElement.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectOtherwiseElement.cs @@ -29,6 +29,7 @@ using System.Collections.Generic; using System; using System.Linq; +using System.Xml; using Microsoft.Build.Exceptions; using Microsoft.Build.Internal; @@ -62,9 +63,9 @@ namespace Microsoft.Build.Construction internal override string XmlName { get { return "Otherwise"; } } - internal override ProjectElement LoadChildElement (string name) + internal override ProjectElement LoadChildElement (XmlReader reader) { - switch (name) { + switch (reader.LocalName) { case "PropertyGroup": var property = ContainingProject.CreatePropertyGroupElement (); AppendChild (property); @@ -79,7 +80,7 @@ namespace Microsoft.Build.Construction return when; default: throw new InvalidProjectFileException (string.Format ( - "Child \"{0}\" is not a known node type.", name)); + "Child \"{0}\" is not a known node type.", reader.LocalName)); } } } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectOutputElement.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectOutputElement.cs index bb5693758d..98d9cb849b 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectOutputElement.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectOutputElement.cs @@ -57,6 +57,13 @@ namespace Microsoft.Build.Construction get { return taskParameter ?? String.Empty; } set { taskParameter = value; } } + #if NET_4_5 + ElementLocation taskParameterLocation; + public ElementLocation TaskParameterLocation { + get { return taskParameterLocation; } + set { taskParameterLocation = value; } + } + #endif internal override string XmlName { get { return "Output"; } } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectPropertyGroupElement.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectPropertyGroupElement.cs index 0050ff5fee..4cee2f9d98 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectPropertyGroupElement.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectPropertyGroupElement.cs @@ -76,9 +76,15 @@ namespace Microsoft.Build.Construction get { return "PropertyGroup"; } } - internal override ProjectElement LoadChildElement (string name) + internal override ProjectElement LoadChildElement (XmlReader reader) { - return AddProperty (name, null); + switch (reader.LocalName) { + case "ItemGroup": + case "PropertyGroup": + throw CreateError (reader, string.Format ("{0} is a reserved name that cannot be used for a property.", reader.LocalName)); + // others need to be checked too, but things like "Project" are somehow allowed... + } + return AddProperty (reader.LocalName, null); } } } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectRootElement.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectRootElement.cs index ddbed1f42c..d7d5d77249 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectRootElement.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectRootElement.cs @@ -63,8 +63,8 @@ namespace Microsoft.Build.Construction string directoryPath; public string DirectoryPath { - get { return directoryPath ?? String.Empty; } - internal set { directoryPath = value; } + get { return directoryPath ?? Directory.GetCurrentDirectory (); } + set { directoryPath = value; } } public ICollection<ProjectPropertyElement> Properties { @@ -170,7 +170,7 @@ namespace Microsoft.Build.Construction string toolsVersion; public string ToolsVersion { - get { return toolsVersion ?? "4.0"; } + get { return toolsVersion ?? string.Empty; } set { toolsVersion = value; } } @@ -185,6 +185,7 @@ namespace Microsoft.Build.Construction ProjectRootElement (ProjectCollection projectCollection) { + ToolsVersion = "4.0"; } public static ProjectRootElement Create () @@ -216,8 +217,9 @@ namespace Microsoft.Build.Construction public static ProjectRootElement Create (XmlReader xmlReader, ProjectCollection projectCollection) { - // yes, this should create en empty project var result = Create (projectCollection); + result.ToolsVersion = null; + result.Load (xmlReader); return result; } @@ -467,6 +469,8 @@ namespace Microsoft.Build.Construction public void Save () { + if (FullPath == null) + throw new InvalidOperationException ("This project was not given the file path to write to."); Save (Encoding); } @@ -518,9 +522,9 @@ namespace Microsoft.Build.Construction } } - internal override ProjectElement LoadChildElement (string name) + internal override ProjectElement LoadChildElement (XmlReader reader) { - switch (name) { + switch (reader.LocalName) { case "PropertyGroup": var prop = CreatePropertyGroupElement (); AppendChild (prop); @@ -538,7 +542,8 @@ namespace Microsoft.Build.Construction AppendChild (def); return def; case "UsingTask": - return AddUsingTask (null, null, null); + var ut = AddUsingTask (null, null, null); + return ut; case "Choose": var choose = CreateChooseElement (); AppendChild (choose); @@ -548,8 +553,7 @@ namespace Microsoft.Build.Construction AppendChild (ext); return ext; default: - throw new InvalidProjectFileException (string.Format ( - "Child \"{0}\" is not a known node type.", name)); + throw CreateError (reader, string.Format ("Child \"{0}\" is not a known node type.", reader.LocalName), -1); } } @@ -573,7 +577,7 @@ namespace Microsoft.Build.Construction internal override void Save (XmlWriter writer) { - writer.WriteStartElement (XmlName, "http://schemas.microsoft.com/developer/msbuild/2003"); + writer.WriteStartElement (XmlName, MSBuildNamespace); SaveValue (writer); writer.WriteEndElement (); } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectTargetElement.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectTargetElement.cs index 66129eb7da..cfaaa60c18 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectTargetElement.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectTargetElement.cs @@ -29,6 +29,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Xml; using Microsoft.Build.Internal; namespace Microsoft.Build.Construction @@ -111,9 +112,30 @@ namespace Microsoft.Build.Construction get { return "Target"; } } - internal override ProjectElement LoadChildElement (string name) +#if NET_4_5 + public ElementLocation AfterTargetsLocation { get; private set; } + public ElementLocation BeforeTargetsLocation { get; private set; } + public ElementLocation DependsOnTargetsLocation { get; private set; } + public ElementLocation InputsLocation { get; private set; } + public ElementLocation KeepDuplicateOutputsLocation { get; private set; } + public ElementLocation NameLocation { get; private set; } + public ElementLocation OutputsLocation { get; private set; } + public ElementLocation ReturnsLocation { get; private set; } +#else + internal ElementLocation AfterTargetsLocation { get; set; } + internal ElementLocation BeforeTargetsLocation { get; set; } + internal ElementLocation DependsOnTargetsLocation { get; set; } + internal ElementLocation InputsLocation { get; set; } + internal ElementLocation KeepDuplicateOutputsLocation { get; set; } + internal ElementLocation LabelLocation { get; set; } + internal ElementLocation NameLocation { get; set; } + internal ElementLocation OutputsLocation { get; set; } + internal ElementLocation ReturnsLocation { get; set; } +#endif + + internal override ProjectElement LoadChildElement (XmlReader reader) { - switch (name) { + switch (reader.LocalName) { case "OnError": var error = new ProjectOnErrorElement (ContainingProject); AppendChild (error); @@ -123,9 +145,10 @@ namespace Microsoft.Build.Construction case "ItemGroup": return AddItemGroup (); default: - return AddTask (name); + return AddTask (reader.LocalName); } } + // This seriously needs to change to become able to fill ElementLocation... internal override void LoadAttribute (string name, string value) { switch (name) { diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectTaskElement.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectTaskElement.cs index 00e0654526..fffe68eb1f 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectTaskElement.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectTaskElement.cs @@ -109,16 +109,16 @@ namespace Microsoft.Build.Construction internal override string XmlName { get { return Name; } } - internal override ProjectElement LoadChildElement (string name) + internal override ProjectElement LoadChildElement (XmlReader reader) { - switch (name) { + switch (reader.LocalName) { case "Output": var output = ContainingProject.CreateOutputElement (null, null, null); AppendChild (output); return output; default: throw new InvalidProjectFileException (string.Format ( - "Child \"{0}\" is not a known node type.", name)); + "Child \"{0}\" is not a known node type.", reader.LocalName)); } } internal override void LoadAttribute (string name, string value) @@ -127,6 +127,17 @@ namespace Microsoft.Build.Construction case "ContinueOnError": ContinueOnError = value; break; +#if NET_4_5 + case "ExecuteTargets": + ExecuteTargets = value; + break; + case "MSBuildArchitecture": + MSBuildArchitecture = value; + break; + case "MSBuildRuntime": + MSBuildRuntime = value; + break; +#endif case "xmlns": break; case "Label": @@ -149,5 +160,15 @@ namespace Microsoft.Build.Construction base.SaveValue (writer); } private Dictionary<string, string> parameters = new Dictionary<string, string> (); + + public string ExecuteTargets { get; set; } + #if NET_4_5 + public ElementLocation ExecuteTargetsLocation { get; set; } + public ElementLocation ContinueOnErrorLocation { get; set; } + public string MSBuildArchitecture { get; set; } + public ElementLocation MSBuildArchitectureLocation { get; set; } + public string MSBuildRuntime { get; set; } + public ElementLocation MSBuildRuntimeLocation { get; set; } + #endif } } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectUsingTaskBodyElement.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectUsingTaskBodyElement.cs index eecc92aa27..36c889dad5 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectUsingTaskBodyElement.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectUsingTaskBodyElement.cs @@ -68,7 +68,6 @@ namespace Microsoft.Build.Construction } internal override void LoadValue (XmlReader reader) { - reader.MoveToElement (); TaskBody = reader.ReadInnerXml (); } } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectUsingTaskElement.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectUsingTaskElement.cs index 83a5d60749..52a1dad01b 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectUsingTaskElement.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectUsingTaskElement.cs @@ -27,6 +27,7 @@ // using System; +using System.Xml; using Microsoft.Build.Exceptions; namespace Microsoft.Build.Construction @@ -107,16 +108,16 @@ namespace Microsoft.Build.Construction internal override string XmlName { get { return "UsingTask"; } } - internal override ProjectElement LoadChildElement (string name) + internal override ProjectElement LoadChildElement (XmlReader reader) { - switch (name) { + switch (reader.LocalName) { case "ParameterGroup": return AddParameterGroup (); case "Task": return AddUsingTaskBody (null, null); default: throw new InvalidProjectFileException (string.Format ( - "Child \"{0}\" is not a known node type.", name)); + "Child \"{0}\" is not a known node type.", reader.LocalName)); } } } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectWhenElement.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectWhenElement.cs index 68040f52c2..0d7331afcb 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectWhenElement.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/ProjectWhenElement.cs @@ -29,6 +29,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Xml; using Microsoft.Build.Exceptions; using Microsoft.Build.Internal; @@ -57,9 +58,9 @@ namespace Microsoft.Build.Construction internal override string XmlName { get { return "When"; } } - internal override ProjectElement LoadChildElement (string name) + internal override ProjectElement LoadChildElement (XmlReader reader) { - switch (name) { + switch (reader.LocalName) { case "PropertyGroup": var property = ContainingProject.CreatePropertyGroupElement (); AppendChild (property); @@ -74,7 +75,7 @@ namespace Microsoft.Build.Construction return when; default: throw new InvalidProjectFileException (string.Format ( - "Child \"{0}\" is not a known node type.", name)); + "Child \"{0}\" is not a known node type.", reader.LocalName)); } } } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/UsingTaskParameterGroupElement.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/UsingTaskParameterGroupElement.cs index 8dd13943aa..ee808a170b 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Construction/UsingTaskParameterGroupElement.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Construction/UsingTaskParameterGroupElement.cs @@ -29,6 +29,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Xml; using Microsoft.Build.Internal; namespace Microsoft.Build.Construction @@ -61,9 +62,9 @@ namespace Microsoft.Build.Construction internal override string XmlName { get { return "ParameterGroup"; } } - internal override ProjectElement LoadChildElement (string name) + internal override ProjectElement LoadChildElement (XmlReader reader) { - return AddParameter (name); + return AddParameter (reader.LocalName); } } } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/Project.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/Project.cs index c87ae5c3df..d384456d52 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/Project.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/Project.cs @@ -4,9 +4,10 @@ // Author: // Leszek Ciesielski (skolima@gmail.com) // Rolf Bjarne Kvinge (rolf@xamarin.com) +// Atsushi Enomoto (atsushi@xamarin.com) // // (C) 2011 Leszek Ciesielski -// Copyright (C) 2011 Xamarin Inc. (http://www.xamarin.com) +// Copyright (C) 2011,2013 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 @@ -36,383 +37,674 @@ using System.Linq; using System.Text; using System.Xml; using Microsoft.Build.Construction; -using Microsoft.Build.Internal; +using Microsoft.Build.Exceptions; using Microsoft.Build.Execution; using Microsoft.Build.Framework; +using Microsoft.Build.Internal; +using Microsoft.Build.Internal.Expressions; using Microsoft.Build.Logging; +using System.Collections; + +// Basically there are two semantic Project object models and their relationship is not obvious +// (apart from Microsoft.Build.Construction.ProjectRootElement which is a "construction rule"). +// +// Microsoft.Build.Evaluation.Project holds some "editable" project model, and it supports +// detailed loader API (such as Items and AllEvaluatedItems). +// ProjectPoperty holds UnevaluatedValue and gives EvaluatedValue too. +// +// Microsoft.Build.Execution.ProjectInstance holds "snapshot" of a project, and it lacks +// detailed loader API. It does not give us Unevaluated property value. +// On the other hand, it supports Targets object model. What Microsoft.Build.Evaluation.Project +// offers there is actually a list of Microsoft.Build.Execution.ProjectInstance objects. +// It should be also noted that only ProjectInstance has Evaluate() method (Project doesn't). +// +// And both API holds different set of descendant types for each and cannot really share the +// loader code. That is lame. +// +// So, can either of them be used to construct the other model? Both API models share the same +// "governor", which is Microsoft.Build.Evaluation.ProjectCollection/ Project is added to +// its LoadedProjects list, while ProjectInstance isn't. Project cannot be loaded to load +// a ProjectInstance, at least within the same ProjectCollection. +// +// On the other hand, can ProjectInstance be used to load a Project? Maybe. Since Project and +// its descendants need Microsoft.Build.Construction.ProjectElement family as its API model +// is part of the public API. Then I still have to understand how those AllEvaluatedItems/ +// AllEvaluatedProperties members make sense. EvaluationCounter is another propery in question. namespace Microsoft.Build.Evaluation { - [DebuggerDisplay("{FullPath} EffectiveToolsVersion={ToolsVersion} #GlobalProperties=" - +"{data.globalProperties.Count} #Properties={data.Properties.Count} #ItemTypes=" - +"{data.ItemTypes.Count} #ItemDefinitions={data.ItemDefinitions.Count} #Items=" - +"{data.Items.Count} #Targets={data.Targets.Count}")] - public class Project - { + [DebuggerDisplay ("{FullPath} EffectiveToolsVersion={ToolsVersion} #GlobalProperties=" + + "{data.globalProperties.Count} #Properties={data.Properties.Count} #ItemTypes=" + + "{data.ItemTypes.Count} #ItemDefinitions={data.ItemDefinitions.Count} #Items=" + + "{data.Items.Count} #Targets={data.Targets.Count}")] + public class Project + { public Project (XmlReader xml) : this (ProjectRootElement.Create (xml)) { } - public Project (XmlReader xml, IDictionary<string, string> globalProperties, - string toolsVersion) + + public Project (XmlReader xml, IDictionary<string, string> globalProperties, + string toolsVersion) : this (ProjectRootElement.Create (xml), globalProperties, toolsVersion) { } - public Project (XmlReader xml, IDictionary<string, string> globalProperties, - string toolsVersion, ProjectCollection projectCollection) + + public Project (XmlReader xml, IDictionary<string, string> globalProperties, + string toolsVersion, ProjectCollection projectCollection) : this (ProjectRootElement.Create (xml), globalProperties, toolsVersion, projectCollection) { } - public Project (XmlReader xml, IDictionary<string, string> globalProperties, - string toolsVersion, ProjectCollection projectCollection, - ProjectLoadSettings loadSettings) + + public Project (XmlReader xml, IDictionary<string, string> globalProperties, + string toolsVersion, ProjectCollection projectCollection, + ProjectLoadSettings loadSettings) : this (ProjectRootElement.Create (xml), globalProperties, toolsVersion, projectCollection, loadSettings) { } - public Project (ProjectRootElement xml) : this(xml, null, null) - { - } - public Project (ProjectRootElement xml, IDictionary<string, string> globalProperties, - string toolsVersion) - : this(xml, globalProperties, toolsVersion, ProjectCollection.GlobalProjectCollection) - { - } - public Project (ProjectRootElement xml, IDictionary<string, string> globalProperties, - string toolsVersion, ProjectCollection projectCollection) - : this(xml, globalProperties, toolsVersion, projectCollection, ProjectLoadSettings.Default) - { - } - - public Project (ProjectRootElement xml, IDictionary<string, string> globalProperties, - string toolsVersion, ProjectCollection projectCollection, - ProjectLoadSettings loadSettings) - { - ProjectCollection = projectCollection; - Xml = xml; - GlobalProperties = globalProperties; - ToolsVersion = toolsVersion; - } - - public Project (string projectFile) : this(projectFile, null, null) - { - } - - public Project (string projectFile, IDictionary<string, string> globalProperties, - string toolsVersion) - : this(projectFile, globalProperties, toolsVersion, ProjectCollection.GlobalProjectCollection, ProjectLoadSettings.Default) - { - } - - public Project (string projectFile, IDictionary<string, string> globalProperties, - string toolsVersion, ProjectCollection projectCollection) - : this(projectFile, globalProperties, toolsVersion, projectCollection, ProjectLoadSettings.Default) - { - } - - public Project (string projectFile, IDictionary<string, string> globalProperties, - string toolsVersion, ProjectCollection projectCollection, - ProjectLoadSettings loadSettings) - { - throw new NotImplementedException (); - } - - public IDictionary<string, string> GlobalProperties { get; private set; } - public ProjectCollection ProjectCollection { get; private set; } - public string ToolsVersion { get; private set; } - public ProjectRootElement Xml { get; private set; } - - public ICollection<ProjectItem> GetItemsIgnoringCondition (string itemType) - { - return new CollectionFromEnumerable<ProjectItem> ( - new FilteredEnumerable<ProjectItemElement> (Xml.Items). - Where (p => p.ItemType.Equals (itemType, StringComparison.OrdinalIgnoreCase)). - Select (p => new ProjectItem(p))); - } - public void RemoveItems (IEnumerable<ProjectItem> items) - { - var removal = new List<ProjectItem> (items); - foreach (var item in removal) { - var parent = item.Xml.Parent; - parent.RemoveChild (item.Xml); - if (parent.Count == 0) - parent.Parent.RemoveChild (parent); - } - } - - public IList<ProjectItem> AddItem (string itemType, string unevaluatedInclude) - { - throw new NotImplementedException (); - } - - public IList<ProjectItem> AddItem (string itemType, string unevaluatedInclude, - IEnumerable<KeyValuePair<string, string>> metadata) - { - throw new NotImplementedException (); - } - - public IList<ProjectItem> AddItemFast (string itemType, string unevaluatedInclude) - { - throw new NotImplementedException (); - } - - public IList<ProjectItem> AddItemFast (string itemType, string unevaluatedInclude, - IEnumerable<KeyValuePair<string, string>> metadata) - { - throw new NotImplementedException (); - } - - public bool Build () - { - throw new NotImplementedException (); - } - - public bool Build (IEnumerable<ILogger> loggers) - { - throw new NotImplementedException (); - } - - public bool Build (string target) - { - throw new NotImplementedException (); - } - - public bool Build (string[] targets) - { - throw new NotImplementedException (); - } - - public bool Build (ILogger logger) - { - throw new NotImplementedException (); - } - - public bool Build (string[] targets, IEnumerable<ILogger> loggers) - { - throw new NotImplementedException (); - } - - public bool Build (IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers) - { - throw new NotImplementedException (); - } - - public bool Build (string target, IEnumerable<ILogger> loggers) - { - throw new NotImplementedException (); - } - - public bool Build (string[] targets, IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers) - { - throw new NotImplementedException (); - } - - public bool Build (string target, IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers) - { - throw new NotImplementedException (); - } - - public ProjectInstance CreateProjectInstance () - { - throw new NotImplementedException (); - } - - public string ExpandString (string unexpandedValue) - { - throw new NotImplementedException (); - } - - public static string GetEvaluatedItemIncludeEscaped (ProjectItem item) - { - throw new NotImplementedException (); - } - - public static string GetEvaluatedItemIncludeEscaped (ProjectItemDefinition item) - { - throw new NotImplementedException (); - } - - public ICollection<ProjectItem> GetItems (string itemType) - { - throw new NotImplementedException (); - } - - public ICollection<ProjectItem> GetItemsByEvaluatedInclude (string evaluatedInclude) - { - throw new NotImplementedException (); - } - - public IEnumerable<ProjectElement> GetLogicalProject () - { - throw new NotImplementedException (); - } - - public static string GetMetadataValueEscaped (ProjectMetadata metadatum) - { - throw new NotImplementedException (); - } - - public static string GetMetadataValueEscaped (ProjectItem item, string name) - { - throw new NotImplementedException (); - } - - public static string GetMetadataValueEscaped (ProjectItemDefinition item, string name) - { - throw new NotImplementedException (); - } - - public string GetPropertyValue (string name) - { - throw new NotImplementedException (); - } - - public static string GetPropertyValueEscaped (ProjectProperty property) - { - throw new NotImplementedException (); - } - - public ProjectProperty GetProperty (string name) - { - throw new NotImplementedException (); - } - - public void MarkDirty () - { - throw new NotImplementedException (); - } - - public void ReevaluateIfNecessary () - { - throw new NotImplementedException (); - } - - public bool RemoveGlobalProperty (string name) - { - throw new NotImplementedException (); - } - - public bool RemoveItem (ProjectItem item) - { - throw new NotImplementedException (); - } - - public bool RemoveProperty (ProjectProperty property) - { - throw new NotImplementedException (); - } - - public void Save () - { - throw new NotImplementedException (); - } - - public void Save (TextWriter writer) - { - throw new NotImplementedException (); - } - - public void Save (string path) - { - throw new NotImplementedException (); - } - - public void Save (Encoding encoding) - { - throw new NotImplementedException (); - } - - public void Save (string path, Encoding encoding) - { - throw new NotImplementedException (); - } - - public void SaveLogicalProject (TextWriter writer) - { - throw new NotImplementedException (); - } - - public bool SetGlobalProperty (string name, string escapedValue) - { - throw new NotImplementedException (); - } - - public ProjectProperty SetProperty (string name, string unevaluatedValue) - { - throw new NotImplementedException (); - } - - public ICollection<ProjectMetadata> AllEvaluatedItemDefinitionMetadata { - get { throw new NotImplementedException (); } - } - - public ICollection<ProjectItem> AllEvaluatedItems { - get { throw new NotImplementedException (); } - } - - public ICollection<ProjectProperty> AllEvaluatedProperties { - get { throw new NotImplementedException (); } - } - - public IDictionary<string, List<string>> ConditionedProperties { - get { throw new NotImplementedException (); } - } - - public string DirectoryPath { - get { throw new NotImplementedException (); } - } - - public bool DisableMarkDirty { get; set; } - - public int EvaluationCounter { - get { throw new NotImplementedException (); } - } - - public string FullPath { - get { throw new NotImplementedException (); } - set { throw new NotImplementedException (); } - } - - public IList<ResolvedImport> Imports { - get { throw new NotImplementedException (); } - } - - public IList<ResolvedImport> ImportsIncludingDuplicates { - get { throw new NotImplementedException (); } - } - - public bool IsBuildEnabled { - get { throw new NotImplementedException (); } - } - - public bool IsDirty { - get { throw new NotImplementedException (); } - } - - public IDictionary<string, ProjectItemDefinition> ItemDefinitions { - get { throw new NotImplementedException (); } - } - - public ICollection<ProjectItem> Items { - get { throw new NotImplementedException (); } - } - - public ICollection<ProjectItem> ItemsIgnoringCondition { - get { throw new NotImplementedException (); } - } - - public ICollection<string> ItemTypes { - get { throw new NotImplementedException (); } - } - - public ICollection<ProjectProperty> Properties { - get { throw new NotImplementedException (); } - } - - public bool SkipEvaluation { get; set; } - - public IDictionary<string, ProjectTargetInstance> Targets { - get { throw new NotImplementedException (); } - } - } + public Project (ProjectRootElement xml) : this (xml, null, null) + { + } + + public Project (ProjectRootElement xml, IDictionary<string, string> globalProperties, + string toolsVersion) + : this (xml, globalProperties, toolsVersion, ProjectCollection.GlobalProjectCollection) + { + } + + public Project (ProjectRootElement xml, IDictionary<string, string> globalProperties, + string toolsVersion, ProjectCollection projectCollection) + : this (xml, globalProperties, toolsVersion, projectCollection, ProjectLoadSettings.Default) + { + } + + public Project (ProjectRootElement xml, IDictionary<string, string> globalProperties, + string toolsVersion, ProjectCollection projectCollection, + ProjectLoadSettings loadSettings) + { + if (projectCollection == null) + throw new ArgumentNullException ("projectCollection"); + this.Xml = xml; + this.GlobalProperties = globalProperties ?? new Dictionary<string, string> (); + this.ToolsVersion = toolsVersion; + this.ProjectCollection = projectCollection; + this.load_settings = loadSettings; + + Initialize (null); + } + + Project (ProjectRootElement imported, Project parent) + { + this.Xml = imported; + this.GlobalProperties = parent.GlobalProperties; + this.ToolsVersion = parent.ToolsVersion; + this.ProjectCollection = parent.ProjectCollection; + this.load_settings = parent.load_settings; + + Initialize (parent); + } + + public Project (string projectFile) + : this (projectFile, null, null) + { + } + + public Project (string projectFile, IDictionary<string, string> globalProperties, + string toolsVersion) + : this (projectFile, globalProperties, toolsVersion, ProjectCollection.GlobalProjectCollection, ProjectLoadSettings.Default) + { + } + + public Project (string projectFile, IDictionary<string, string> globalProperties, + string toolsVersion, ProjectCollection projectCollection) + : this (projectFile, globalProperties, toolsVersion, projectCollection, ProjectLoadSettings.Default) + { + } + + public Project (string projectFile, IDictionary<string, string> globalProperties, + string toolsVersion, ProjectCollection projectCollection, + ProjectLoadSettings loadSettings) + : this (ProjectRootElement.Create (projectFile), globalProperties, toolsVersion, projectCollection, loadSettings) + { + } + + ProjectLoadSettings load_settings; + + public IDictionary<string, string> GlobalProperties { get; private set; } + + public ProjectCollection ProjectCollection { get; private set; } + + public string ToolsVersion { get; private set; } + + public ProjectRootElement Xml { get; private set; } + + string dir_path; + Dictionary<string, ProjectItemDefinition> item_definitions; + List<ResolvedImport> raw_imports; + List<ProjectItem> raw_items; + List<ProjectItem> all_evaluated_items; + List<ProjectProperty> properties; + Dictionary<string, ProjectTargetInstance> targets; + + void Initialize (Project parent) + { + dir_path = Directory.GetCurrentDirectory (); + raw_imports = new List<ResolvedImport> (); + item_definitions = new Dictionary<string, ProjectItemDefinition> (); + targets = new Dictionary<string, ProjectTargetInstance> (); + raw_items = new List<ProjectItem> (); + + // FIXME: this is likely hack. Test ImportedProject.Properties to see what exactly should happen. + if (parent != null) { + properties = parent.properties; + } else { + properties = new List<ProjectProperty> (); + + foreach (DictionaryEntry p in Environment.GetEnvironmentVariables ()) + // FIXME: this is kind of workaround for unavoidable issue that PLATFORM=* is actually given + // on some platforms and that prevents setting default "PLATFORM=AnyCPU" property. + if (!string.Equals ("PLATFORM", (string) p.Key, StringComparison.OrdinalIgnoreCase)) + this.properties.Add (new EnvironmentProjectProperty (this, (string)p.Key, (string)p.Value)); + foreach (var p in GlobalProperties) + this.properties.Add (new GlobalProjectProperty (this, p.Key, p.Value)); + var tools = ProjectCollection.GetToolset (this.ToolsVersion) ?? ProjectCollection.GetToolset (this.ProjectCollection.DefaultToolsVersion); + foreach (var p in ProjectCollection.GetReservedProperties (tools, this)) + this.properties.Add (p); + foreach (var p in ProjectCollection.GetWellKnownProperties (this)) + this.properties.Add (p); + } + + ProcessXml (parent); + + ProjectCollection.AddProject (this); + } + + void ProcessXml (Project parent) + { + // this needs to be initialized here (regardless of that items won't be evaluated at property evaluation; + // Conditions could incorrectly reference items and lack of this list causes NRE. + all_evaluated_items = new List<ProjectItem> (); + + // property evaluation happens couple of times. + // At first step, all non-imported properties are evaluated TOO, WHILE those properties are being evaluated. + // This means, Include and IncludeGroup elements with Condition attribute MAY contain references to + // properties and they will be expanded. + var elements = EvaluatePropertiesAndImports (Xml.Children).ToArray (); // ToArray(): to not lazily evaluate elements. + + // next, evaluate items + EvaluateItems (elements); + + // finally, evaluate targets and tasks + EvaluateTargets (elements); + } + + IEnumerable<ProjectElement> EvaluatePropertiesAndImports (IEnumerable<ProjectElement> elements) + { + // First step: evaluate Properties + foreach (var child in elements) { + yield return child; + var pge = child as ProjectPropertyGroupElement; + if (pge != null && Evaluate (pge.Condition)) + foreach (var p in pge.Properties) + // do not allow overwriting reserved or well-known properties by user + if (!this.properties.Any (_ => (_.IsReservedProperty || _.IsWellKnownProperty) && _.Name.Equals (p.Name, StringComparison.InvariantCultureIgnoreCase))) + if (Evaluate (p.Condition)) + this.properties.Add (new XmlProjectProperty (this, p, PropertyType.Normal, ProjectCollection.OngoingImports.Any ())); + + var ige = child as ProjectImportGroupElement; + if (ige != null && Evaluate (ige.Condition)) { + foreach (var incc in ige.Imports) { + foreach (var e in Import (incc)) + yield return e; + } + } + var inc = child as ProjectImportElement; + if (inc != null && Evaluate (inc.Condition)) + foreach (var e in Import (inc)) + yield return e; + } + } + + internal IEnumerable<T> GetAllItems<T> (string include, string exclude, Func<string,T> creator, Func<string,ITaskItem> taskItemCreator, Func<string,bool> itemTypeCheck, Action<T,string> assignRecurse) + { + return ProjectCollection.GetAllItems<T> (ExpandString, include, exclude, creator, taskItemCreator, DirectoryPath, assignRecurse, + t => all_evaluated_items.Any (i => i.EvaluatedInclude == t.ItemSpec && itemTypeCheck (i.ItemType))); + } + + void EvaluateItems (IEnumerable<ProjectElement> elements) + { + foreach (var child in elements) { + var ige = child as ProjectItemGroupElement; + if (ige != null) { + foreach (var p in ige.Items) { + if (!Evaluate (ige.Condition) || !Evaluate (p.Condition)) + continue; + Func<string,ProjectItem> creator = s => new ProjectItem (this, p, s); + foreach (var item in GetAllItems<ProjectItem> (p.Include, p.Exclude, creator, s => new ProjectTaskItem (p, s), it => string.Equals (it, p.ItemType, StringComparison.OrdinalIgnoreCase), (t, s) => t.RecursiveDir = s)) { + raw_items.Add (item); + all_evaluated_items.Add (item); + } + } + } + var def = child as ProjectItemDefinitionGroupElement; + if (def != null) { + foreach (var p in def.ItemDefinitions) { + if (Evaluate (p.Condition)) { + ProjectItemDefinition existing; + if (!item_definitions.TryGetValue (p.ItemType, out existing)) + item_definitions.Add (p.ItemType, (existing = new ProjectItemDefinition (this, p.ItemType))); + existing.AddItems (p); + } + } + } + } + all_evaluated_items.Sort ((p1, p2) => string.Compare (p1.ItemType, p2.ItemType, StringComparison.OrdinalIgnoreCase)); + } + + void EvaluateTargets (IEnumerable<ProjectElement> elements) + { + foreach (var child in elements) { + var te = child as ProjectTargetElement; + if (te != null) + this.targets.Add (te.Name, new ProjectTargetInstance (te)); + } + } + + IEnumerable<ProjectElement> Import (ProjectImportElement import) + { + string dir = ProjectCollection.GetEvaluationTimeThisFileDirectory (() => FullPath); + string path = WindowsCompatibilityExtensions.NormalizeFilePath (ExpandString (import.Project)); + path = Path.IsPathRooted (path) ? path : dir != null ? Path.Combine (dir, path) : Path.GetFullPath (path); + if (ProjectCollection.OngoingImports.Contains (path)) { + switch (load_settings) { + case ProjectLoadSettings.RejectCircularImports: + throw new InvalidProjectFileException (import.Location, null, string.Format ("Circular imports was detected: {0} (resolved as \"{1}\") is already on \"importing\" stack", import.Project, path)); + } + return new ProjectElement [0]; // do not import circular references + } + ProjectCollection.OngoingImports.Push (path); + try { + using (var reader = XmlReader.Create (path)) { + var root = ProjectRootElement.Create (reader, ProjectCollection); + raw_imports.Add (new ResolvedImport (import, root, true)); + return this.EvaluatePropertiesAndImports (root.Children).ToArray (); + } + } finally { + ProjectCollection.OngoingImports.Pop (); + } + } + + public ICollection<ProjectItem> GetItemsIgnoringCondition (string itemType) + { + return new CollectionFromEnumerable<ProjectItem> (raw_items.Where (p => p.ItemType.Equals (itemType, StringComparison.OrdinalIgnoreCase))); + } + + public void RemoveItems (IEnumerable<ProjectItem> items) + { + var removal = new List<ProjectItem> (items); + foreach (var item in removal) { + var parent = item.Xml.Parent; + parent.RemoveChild (item.Xml); + if (parent.Count == 0) + parent.Parent.RemoveChild (parent); + } + } + + static readonly Dictionary<string, string> empty_metadata = new Dictionary<string, string> (); + + public IList<ProjectItem> AddItem (string itemType, string unevaluatedInclude) + { + return AddItem (itemType, unevaluatedInclude, empty_metadata); + } + + public IList<ProjectItem> AddItem (string itemType, string unevaluatedInclude, + IEnumerable<KeyValuePair<string, string>> metadata) + { + // FIXME: needs several check that AddItemFast() does not process (see MSDN for details). + + return AddItemFast (itemType, unevaluatedInclude, metadata); + } + + public IList<ProjectItem> AddItemFast (string itemType, string unevaluatedInclude) + { + return AddItemFast (itemType, unevaluatedInclude, empty_metadata); + } + + public IList<ProjectItem> AddItemFast (string itemType, string unevaluatedInclude, + IEnumerable<KeyValuePair<string, string>> metadata) + { + throw new NotImplementedException (); + } + + static readonly char [] target_sep = new char[] {';'}; + + public bool Build () + { + return Build (Xml.DefaultTargets.Split (target_sep, StringSplitOptions.RemoveEmptyEntries)); + } + + public bool Build (IEnumerable<ILogger> loggers) + { + return Build (Xml.DefaultTargets.Split (target_sep, StringSplitOptions.RemoveEmptyEntries), loggers); + } + + public bool Build (string target) + { + return string.IsNullOrWhiteSpace (target) ? Build () : Build (new string [] {target}); + } + + public bool Build (string[] targets) + { + return Build (targets, new ILogger [0]); + } + + public bool Build (ILogger logger) + { + return Build (Xml.DefaultTargets.Split (target_sep, StringSplitOptions.RemoveEmptyEntries), new ILogger [] {logger}); + } + + public bool Build (string[] targets, IEnumerable<ILogger> loggers) + { + return Build (targets, loggers, new ForwardingLoggerRecord [0]); + } + + public bool Build (IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers) + { + return Build (Xml.DefaultTargets.Split (target_sep, StringSplitOptions.RemoveEmptyEntries), loggers, remoteLoggers); + } + + public bool Build (string target, IEnumerable<ILogger> loggers) + { + return Build (new string [] { target }, loggers); + } + + public bool Build (string[] targets, IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers) + { + // Unlike ProjectInstance.Build(), there is no place to fill outputs by targets, so ignore them + // (i.e. we don't use the overload with output). + // + // This does not check FullPath, so don't call GetProjectInstanceForBuild() directly. + return new BuildManager ().GetProjectInstanceForBuildInternal (this).Build (targets, loggers, remoteLoggers); + } + + public bool Build (string target, IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers) + { + return Build (new string [] { target }, loggers, remoteLoggers); + } + + public ProjectInstance CreateProjectInstance () + { + var ret = new ProjectInstance (Xml, GlobalProperties, ToolsVersion, ProjectCollection); + // FIXME: maybe fill other properties to the result. + return ret; + } + + bool Evaluate (string unexpandedValue) + { + return string.IsNullOrWhiteSpace (unexpandedValue) || new ExpressionEvaluator (this, null).EvaluateAsBoolean (unexpandedValue); + } + + public string ExpandString (string unexpandedValue) + { + return ExpandString (unexpandedValue, null); + } + + string ExpandString (string unexpandedValue, string replacementForMissingStuff) + { + return new ExpressionEvaluator (this, replacementForMissingStuff).Evaluate (unexpandedValue); + } + + public static string GetEvaluatedItemIncludeEscaped (ProjectItem item) + { + return ProjectCollection.Escape (item.EvaluatedInclude); + } + + public static string GetEvaluatedItemIncludeEscaped (ProjectItemDefinition item) + { + // ?? ItemDefinition does not have Include attribute. What's the point here? + throw new NotImplementedException (); + } + + public ICollection<ProjectItem> GetItems (string itemType) + { + return new CollectionFromEnumerable<ProjectItem> (Items.Where (p => p.ItemType.Equals (itemType, StringComparison.OrdinalIgnoreCase))); + } + + public ICollection<ProjectItem> GetItemsByEvaluatedInclude (string evaluatedInclude) + { + return new CollectionFromEnumerable<ProjectItem> (Items.Where (p => p.EvaluatedInclude.Equals (evaluatedInclude, StringComparison.OrdinalIgnoreCase))); + } + + public IEnumerable<ProjectElement> GetLogicalProject () + { + throw new NotImplementedException (); + } + + public static string GetMetadataValueEscaped (ProjectMetadata metadatum) + { + return ProjectCollection.Escape (metadatum.EvaluatedValue); + } + + public static string GetMetadataValueEscaped (ProjectItem item, string name) + { + var md = item.Metadata.FirstOrDefault (m => m.Name.Equals (name, StringComparison.OrdinalIgnoreCase)); + return md != null ? ProjectCollection.Escape (md.EvaluatedValue) : null; + } + + public static string GetMetadataValueEscaped (ProjectItemDefinition item, string name) + { + var md = item.Metadata.FirstOrDefault (m => m.Name.Equals (name, StringComparison.OrdinalIgnoreCase)); + return md != null ? ProjectCollection.Escape (md.EvaluatedValue) : null; + } + + public string GetPropertyValue (string name) + { + var prop = GetProperty (name); + return prop != null ? prop.EvaluatedValue : string.Empty; + } + + public static string GetPropertyValueEscaped (ProjectProperty property) + { + // WTF happens here. + //return ProjectCollection.Escape (property.EvaluatedValue); + return property.EvaluatedValue; + } + + public ProjectProperty GetProperty (string name) + { + return properties.FirstOrDefault (p => p.Name.Equals (name, StringComparison.OrdinalIgnoreCase)); + } + + public void MarkDirty () + { + if (!DisableMarkDirty) + is_dirty = true; + } + + public void ReevaluateIfNecessary () + { + throw new NotImplementedException (); + } + + public bool RemoveGlobalProperty (string name) + { + throw new NotImplementedException (); + } + + public bool RemoveItem (ProjectItem item) + { + throw new NotImplementedException (); + } + + public bool RemoveProperty (ProjectProperty property) + { + var removed = properties.FirstOrDefault (p => p.Name.Equals (property.Name, StringComparison.OrdinalIgnoreCase)); + if (removed == null) + return false; + properties.Remove (removed); + return true; + } + + public void Save () + { + Xml.Save (); + } + + public void Save (TextWriter writer) + { + Xml.Save (writer); + } + + public void Save (string path) + { + Save (path, Encoding.Default); + } + + public void Save (Encoding encoding) + { + Save (FullPath, encoding); + } + + public void Save (string path, Encoding encoding) + { + using (var writer = new StreamWriter (path, false, encoding)) + Save (writer); + } + + public void SaveLogicalProject (TextWriter writer) + { + throw new NotImplementedException (); + } + + public bool SetGlobalProperty (string name, string escapedValue) + { + throw new NotImplementedException (); + } + + public ProjectProperty SetProperty (string name, string unevaluatedValue) + { + var p = new ManuallyAddedProjectProperty (this, name, unevaluatedValue); + properties.Add (p); + return p; + } + + public ICollection<ProjectMetadata> AllEvaluatedItemDefinitionMetadata { + get { throw new NotImplementedException (); } + } + + public ICollection<ProjectItem> AllEvaluatedItems { + get { return all_evaluated_items; } + } + + public ICollection<ProjectProperty> AllEvaluatedProperties { + get { return properties; } + } + + public IDictionary<string, List<string>> ConditionedProperties { + get { + // this property returns different instances every time. + var dic = new Dictionary<string, List<string>> (); + + // but I dunno HOW this evaluates + + throw new NotImplementedException (); + } + } + + public string DirectoryPath { + get { return dir_path; } + } + + public bool DisableMarkDirty { get; set; } + + public int EvaluationCounter { + get { throw new NotImplementedException (); } + } + + public string FullPath { + get { return Xml.FullPath; } + set { Xml.FullPath = value; } + } + + class ResolvedImportComparer : IEqualityComparer<ResolvedImport> + { + public static ResolvedImportComparer Instance = new ResolvedImportComparer (); + + public bool Equals (ResolvedImport x, ResolvedImport y) + { + return x.ImportedProject.FullPath.Equals (y.ImportedProject.FullPath); + } + public int GetHashCode (ResolvedImport obj) + { + return obj.ImportedProject.FullPath.GetHashCode (); + } + } + + public IList<ResolvedImport> Imports { + get { return raw_imports.Distinct (ResolvedImportComparer.Instance).ToList (); } + } + + public IList<ResolvedImport> ImportsIncludingDuplicates { + get { return raw_imports; } + } + + public bool IsBuildEnabled { + get { return ProjectCollection.IsBuildEnabled; } + } + + bool is_dirty; + public bool IsDirty { + get { return is_dirty; } + } + + public IDictionary<string, ProjectItemDefinition> ItemDefinitions { + get { return item_definitions; } + } + + [MonoTODO ("should be different from AllEvaluatedItems")] + public ICollection<ProjectItem> Items { + get { return AllEvaluatedItems; } + } + + public ICollection<ProjectItem> ItemsIgnoringCondition { + get { return raw_items; } + } + + public ICollection<string> ItemTypes { + get { return new CollectionFromEnumerable<string> (raw_items.Select (i => i.ItemType).Distinct ()); } + } + + [MonoTODO ("should be different from AllEvaluatedProperties")] + public ICollection<ProjectProperty> Properties { + get { return AllEvaluatedProperties; } + } + + public bool SkipEvaluation { get; set; } + + public IDictionary<string, ProjectTargetInstance> Targets { + get { return targets; } + } + + // These are required for reserved property, represents dynamically changing property values. + // This should resolve to either the project file path or that of the imported file. + internal string GetEvaluationTimeThisFileDirectory () + { + var file = GetEvaluationTimeThisFile (); + var dir = Path.IsPathRooted (file) ? Path.GetDirectoryName (file) : Directory.GetCurrentDirectory (); + return dir + Path.DirectorySeparatorChar; + } + + internal string GetEvaluationTimeThisFile () + { + return ProjectCollection.OngoingImports.Count > 0 ? ProjectCollection.OngoingImports.Peek () : FullPath ?? string.Empty; + } + + internal string GetFullPath (string pathRelativeToProject) + { + if (Path.IsPathRooted (pathRelativeToProject)) + return pathRelativeToProject; + return Path.GetFullPath (Path.Combine (DirectoryPath, pathRelativeToProject)); + } + } } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Logging/ColorResetter.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ProjectChangedEventArgs.cs index 5bd3da3e7c..5372642ae0 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Logging/ColorResetter.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ProjectChangedEventArgs.cs @@ -1,9 +1,10 @@ -// ColorResetter.cs +// +// ProjectChangedEventArgs.cs // // Author: -// Rolf Bjarne Kvinge (rolf@xamarin.com) +// Atsushi Enomoto <atsushi@xamarin.com> // -// Copyright (C) 2011 Xamarin Inc. +// Copyright (C) 2013 Xamarin Inc. // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the @@ -15,7 +16,7 @@ // // 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 @@ -25,8 +26,17 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -namespace Microsoft.Build.Logging +using System; +using System.Linq; + +namespace Microsoft.Build.Evaluation { - public delegate void ColorResetter (); + public class ProjectChangedEventArgs : EventArgs + { + internal ProjectChangedEventArgs (Project project) + { + Project = project; + } + public Project Project { get; private set; } + } } - diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ProjectCollection.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ProjectCollection.cs index f5b2826961..ab3ee06638 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ProjectCollection.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ProjectCollection.cs @@ -4,9 +4,10 @@ // Author: // Leszek Ciesielski (skolima@gmail.com) // Rolf Bjarne Kvinge (rolf@xamarin.com) +// Atsushi Enomoto (atsushi@xamarin.com) // // (C) 2011 Leszek Ciesielski -// Copyright (C) 2011 Xamarin Inc. +// Copyright (C) 2011,2013 Xamarin Inc. // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the @@ -29,97 +30,468 @@ // using Microsoft.Build.Construction; +using Microsoft.Build.Execution; using Microsoft.Build.Framework; using Microsoft.Build.Logging; - +using Microsoft.Build.Utilities; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Xml; +using System.Reflection; +using System.Globalization; namespace Microsoft.Build.Evaluation { - public class ProjectCollection : IDisposable - { - public ProjectCollection () - { - } - - public ProjectCollection (IDictionary<string, string> globalProperties) - : this (globalProperties, null, ToolsetDefinitionLocations.Registry | ToolsetDefinitionLocations.ConfigurationFile) - { - } - - public ProjectCollection (ToolsetDefinitionLocations toolsetDefinitionLocations) - : this (null, null, toolsetDefinitionLocations) - { - } - - public ProjectCollection (IDictionary<string, string> globalProperties, IEnumerable<ILogger> loggers, - ToolsetDefinitionLocations toolsetDefinitionLocations) - : this (globalProperties, loggers, null, toolsetDefinitionLocations, 1, false) - { - } - - public ProjectCollection (IDictionary<string, string> globalProperties, - IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers, - ToolsetDefinitionLocations toolsetDefinitionLocations, - int maxNodeCount, bool onlyLogCriticalEvents) - { - throw new NotImplementedException (); - } - - public static string Escape (string unescapedString) - { - return unescapedString; - } - - public static ProjectCollection GlobalProjectCollection { - get { return globalProjectCollection; } - } - - public void Dispose () - { - Dispose (true); - GC.SuppressFinalize (this); - } - - protected virtual void Dispose (bool disposing) - { - if (disposing) { - } - } - - static ProjectCollection globalProjectCollection = new ProjectCollection (); - - public ICollection<Project> GetLoadedProjects (string fullPath) - { - throw new NotImplementedException (); - } - - public ToolsetDefinitionLocations ToolsetLocations { - get { throw new NotImplementedException (); } - } + public class ProjectCollection : IDisposable + { + public delegate void ProjectAddedEventHandler (object target, ProjectAddedToProjectCollectionEventArgs args); + + public class ProjectAddedToProjectCollectionEventArgs : EventArgs + { + public ProjectAddedToProjectCollectionEventArgs (ProjectRootElement project) + { + if (project == null) + throw new ArgumentNullException ("project"); + ProjectRootElement = project; + } + + public ProjectRootElement ProjectRootElement { get; private set; } + } + + // static members + + static readonly ProjectCollection global_project_collection; + + static ProjectCollection () + { + #if NET_4_5 + global_project_collection = new ProjectCollection (new ReadOnlyDictionary<string, string> (new Dictionary<string, string> ())); + #else + global_project_collection = new ProjectCollection (new Dictionary<string, string> ()); + #endif + } + + public static string Escape (string unescapedString) + { + return Mono.XBuild.Utilities.MSBuildUtils.Escape (unescapedString); + } + + public static string Unescape (string escapedString) + { + return Mono.XBuild.Utilities.MSBuildUtils.Unescape (escapedString); + } + + public static ProjectCollection GlobalProjectCollection { + get { return global_project_collection; } + } + + // semantic model part + + public ProjectCollection () + : this (null) + { + } + + public ProjectCollection (IDictionary<string, string> globalProperties) + : this (globalProperties, null, ToolsetDefinitionLocations.Registry | ToolsetDefinitionLocations.ConfigurationFile) + { + } + public ProjectCollection (ToolsetDefinitionLocations toolsetDefinitionLocations) + : this (null, null, toolsetDefinitionLocations) + { + } + + public ProjectCollection (IDictionary<string, string> globalProperties, IEnumerable<ILogger> loggers, + ToolsetDefinitionLocations toolsetDefinitionLocations) + : this (globalProperties, loggers, null, toolsetDefinitionLocations, 1, false) + { + } + + public ProjectCollection (IDictionary<string, string> globalProperties, + IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers, + ToolsetDefinitionLocations toolsetDefinitionLocations, + int maxNodeCount, bool onlyLogCriticalEvents) + { + global_properties = globalProperties ?? new Dictionary<string, string> (); + this.loggers = loggers != null ? loggers.ToList () : new List<ILogger> (); + toolset_locations = toolsetDefinitionLocations; + MaxNodeCount = maxNodeCount; + OnlyLogCriticalEvents = onlyLogCriticalEvents; + + LoadDefaultToolsets (); + } + + [MonoTODO ("not fired yet")] + public event ProjectAddedEventHandler ProjectAdded; + [MonoTODO ("not fired yet")] + public event EventHandler<ProjectChangedEventArgs> ProjectChanged; + [MonoTODO ("not fired yet")] + public event EventHandler<ProjectCollectionChangedEventArgs> ProjectCollectionChanged; + [MonoTODO ("not fired yet")] + public event EventHandler<ProjectXmlChangedEventArgs> ProjectXmlChanged; + + public void AddProject (Project project) + { + this.loaded_projects.Add (project); + if (ProjectAdded != null) + ProjectAdded (this, new ProjectAddedToProjectCollectionEventArgs (project.Xml)); + } + + public int Count { + get { return loaded_projects.Count; } + } + + string default_tools_version; + public string DefaultToolsVersion { + get { return default_tools_version; } + set { + if (GetToolset (value) == null) + throw new InvalidOperationException (string.Format ("Toolset '{0}' does not exist", value)); + default_tools_version = value; + } + } + + public void Dispose () + { + Dispose (true); + GC.SuppressFinalize (this); + } + + protected virtual void Dispose (bool disposing) + { + if (disposing) { + } + } + + public ICollection<Project> GetLoadedProjects (string fullPath) + { + return LoadedProjects.Where (p => p.FullPath != null && Path.GetFullPath (p.FullPath) == Path.GetFullPath (fullPath)).ToList (); + } + + readonly IDictionary<string, string> global_properties; + + public IDictionary<string, string> GlobalProperties { + get { return global_properties; } + } + + readonly List<Project> loaded_projects = new List<Project> (); + + public Project LoadProject (string fileName) + { + return LoadProject (fileName, DefaultToolsVersion); + } + + public Project LoadProject (string fileName, string toolsVersion) + { + return LoadProject (fileName, null, toolsVersion); + } + + public Project LoadProject (string fileName, IDictionary<string,string> globalProperties, string toolsVersion) + { + var ret = new Project (fileName, globalProperties, toolsVersion); + loaded_projects.Add (ret); + return ret; + } + + // These methods somehow don't add the project to ProjectCollection... + public Project LoadProject (XmlReader xmlReader) + { + return LoadProject (xmlReader, DefaultToolsVersion); + } + + public Project LoadProject (XmlReader xmlReader, string toolsVersion) + { + return LoadProject (xmlReader, null, toolsVersion); + } + + public Project LoadProject (XmlReader xmlReader, IDictionary<string,string> globalProperties, string toolsVersion) + { + return new Project (xmlReader, globalProperties, toolsVersion); + } + + public ICollection<Project> LoadedProjects { + get { return loaded_projects; } + } + + readonly List<ILogger> loggers = new List<ILogger> (); + [MonoTODO] + public ICollection<ILogger> Loggers { + get { return loggers; } + } + + [MonoTODO] + public bool OnlyLogCriticalEvents { get; set; } + + [MonoTODO] + public bool SkipEvaluation { get; set; } + + readonly ToolsetDefinitionLocations toolset_locations; + public ToolsetDefinitionLocations ToolsetLocations { + get { return toolset_locations; } + } + + readonly List<Toolset> toolsets = new List<Toolset> (); + // so what should we do without ToolLocationHelper in Microsoft.Build.Utilities.dll? There is no reference to it in this dll. public ICollection<Toolset> Toolsets { - get { throw new NotImplementedException (); } - } + // For ConfigurationFile and None, they cannot be added externally. + get { return (ToolsetLocations & ToolsetDefinitionLocations.Registry) != 0 ? toolsets : toolsets.ToList (); } + } + + public Toolset GetToolset (string toolsVersion) + { + return Toolsets.FirstOrDefault (t => t.ToolsVersion == toolsVersion); + } + + //FIXME: should also support config file, depending on ToolsetLocations + void LoadDefaultToolsets () + { + AddToolset (new Toolset ("2.0", + ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version20), this, null)); + AddToolset (new Toolset ("3.0", + ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version30), this, null)); + AddToolset (new Toolset ("3.5", + ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version35), this, null)); +#if NET_4_0 + AddToolset (new Toolset ("4.0", + ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version40), this, null)); +#endif +#if XBUILD_12 + AddToolset (new Toolset ("12.0", ToolLocationHelper.GetPathToBuildTools ("12.0"), this, null)); +#endif + default_tools_version = toolsets.First ().ToolsVersion; + } + + [MonoTODO ("not verified at all")] + public void AddToolset (Toolset toolset) + { + toolsets.Add (toolset); + } + + [MonoTODO ("not verified at all")] + public void RemoveAllToolsets () + { + toolsets.Clear (); + } + + [MonoTODO ("not verified at all")] + public void RegisterLogger (ILogger logger) + { + loggers.Add (logger); + } + + [MonoTODO ("not verified at all")] + public void RegisterLoggers (IEnumerable<ILogger> loggers) + { + foreach (var logger in loggers) + this.loggers.Add (logger); + } public void UnloadAllProjects () { - throw new NotImplementedException (); + throw new NotImplementedException (); } + [MonoTODO ("Not verified at all")] public void UnloadProject (Project project) { - throw new NotImplementedException (); + this.loaded_projects.Remove (project); } + [MonoTODO ("Not verified at all")] public void UnloadProject (ProjectRootElement projectRootElement) { - throw new NotImplementedException (); + foreach (var proj in loaded_projects.Where (p => p.Xml == projectRootElement).ToArray ()) + UnloadProject (proj); } public static Version Version { - get { throw new NotImplementedException (); } - } - } + get { throw new NotImplementedException (); } + } + + // Execution part + + [MonoTODO] + public bool DisableMarkDirty { get; set; } + + [MonoTODO] + public HostServices HostServices { get; set; } + + [MonoTODO] + public bool IsBuildEnabled { get; set; } + + internal string BuildStartupDirectory { get; set; } + + internal int MaxNodeCount { get; private set; } + + Stack<string> ongoing_imports = new Stack<string> (); + + internal Stack<string> OngoingImports { + get { return ongoing_imports; } + } + + // common part + internal static IEnumerable<EnvironmentProjectProperty> GetWellKnownProperties (Project project) + { + Func<string,string,EnvironmentProjectProperty> create = (name, value) => new EnvironmentProjectProperty (project, name, value, true); + return GetWellKnownProperties (create); + } + + internal static IEnumerable<ProjectPropertyInstance> GetWellKnownProperties (ProjectInstance project) + { + Func<string,string,ProjectPropertyInstance> create = (name, value) => new ProjectPropertyInstance (name, true, value); + return GetWellKnownProperties (create); + } + + static IEnumerable<T> GetWellKnownProperties<T> (Func<string,string,T> create) + { + var ext = Environment.GetEnvironmentVariable ("MSBuildExtensionsPath") ?? DefaultExtensionsPath; + yield return create ("MSBuildExtensionsPath", ext); + var ext32 = Environment.GetEnvironmentVariable ("MSBuildExtensionsPath32") ?? DefaultExtensionsPath; + yield return create ("MSBuildExtensionsPath32", ext32); + var ext64 = Environment.GetEnvironmentVariable ("MSBuildExtensionsPath64") ?? DefaultExtensionsPath; + yield return create ("MSBuildExtensionsPath64", ext64); + } + + static string extensions_path; + internal static string DefaultExtensionsPath { + get { + if (extensions_path == null) { + // NOTE: code from mcs/tools/gacutil/driver.cs + PropertyInfo gac = typeof (System.Environment).GetProperty ( + "GacPath", BindingFlags.Static | BindingFlags.NonPublic); + + if (gac != null) { + MethodInfo get_gac = gac.GetGetMethod (true); + string gac_path = (string) get_gac.Invoke (null, null); + extensions_path = Path.GetFullPath (Path.Combine ( + gac_path, Path.Combine ("..", "xbuild"))); + } + } + return extensions_path; + } + } + + internal IEnumerable<ReservedProjectProperty> GetReservedProperties (Toolset toolset, Project project) + { + Func<string,Func<string>,ReservedProjectProperty> create = (name, value) => new ReservedProjectProperty (project, name, value); + return GetReservedProperties<ReservedProjectProperty> (toolset, project.Xml, create, () => project.FullPath); + } + + internal IEnumerable<ProjectPropertyInstance> GetReservedProperties (Toolset toolset, ProjectInstance project, ProjectRootElement xml) + { + Func<string,Func<string>,ProjectPropertyInstance> create = (name, value) => new ProjectPropertyInstance (name, true, null, value); + return GetReservedProperties<ProjectPropertyInstance> (toolset, xml, create, () => project.FullPath); + } + + // seealso http://msdn.microsoft.com/en-us/library/ms164309.aspx + IEnumerable<T> GetReservedProperties<T> (Toolset toolset, ProjectRootElement project, Func<string,Func<string>,T> create, Func<string> projectFullPath) + { + yield return create ("MSBuildBinPath", () => toolset.ToolsPath); + // FIXME: add MSBuildLastTaskResult + // FIXME: add MSBuildNodeCount + // FIXME: add MSBuildProgramFiles32 + yield return create ("MSBuildProjectDefaultTargets", () => project.DefaultTargets); + yield return create ("MSBuildProjectDirectory", () => project.DirectoryPath + Path.DirectorySeparatorChar); + yield return create ("MSBuildProjectDirectoryNoRoot", () => project.DirectoryPath.Substring (Path.GetPathRoot (project.DirectoryPath).Length)); + yield return create ("MSBuildProjectExtension", () => Path.GetExtension (project.FullPath)); + yield return create ("MSBuildProjectFile", () => Path.GetFileName (project.FullPath)); + yield return create ("MSBuildProjectFullPath", () => project.FullPath); + yield return create ("MSBuildProjectName", () => Path.GetFileNameWithoutExtension (project.FullPath)); + yield return create ("MSBuildStartupDirectory", () => BuildStartupDirectory); + yield return create ("MSBuildThisFile", () => Path.GetFileName (GetEvaluationTimeThisFile (projectFullPath))); + yield return create ("MSBuildThisFileFullPath", () => GetEvaluationTimeThisFile (projectFullPath)); + yield return create ("MSBuildThisFileName", () => Path.GetFileNameWithoutExtension (GetEvaluationTimeThisFile (projectFullPath))); + yield return create ("MSBuildThisFileExtension", () => Path.GetExtension (GetEvaluationTimeThisFile (projectFullPath))); + + yield return create ("MSBuildThisFileDirectory", () => Path.GetDirectoryName (GetEvaluationTimeThisFileDirectory (projectFullPath))); + yield return create ("MSBuildThisFileDirectoryNoRoot", () => { + string dir = GetEvaluationTimeThisFileDirectory (projectFullPath) + Path.DirectorySeparatorChar; + return dir.Substring (Path.GetPathRoot (dir).Length); + }); + yield return create ("MSBuildToolsPath", () => toolset.ToolsPath); + yield return create ("MSBuildToolsVersion", () => toolset.ToolsVersion); + } + + // These are required for reserved property, represents dynamically changing property values. + // This should resolve to either the project file path or that of the imported file. + internal string GetEvaluationTimeThisFileDirectory (Func<string> nonImportingTimeFullPath) + { + var file = GetEvaluationTimeThisFile (nonImportingTimeFullPath); + var dir = Path.IsPathRooted (file) ? Path.GetDirectoryName (file) : Directory.GetCurrentDirectory (); + return dir + Path.DirectorySeparatorChar; + } + + internal string GetEvaluationTimeThisFile (Func<string> nonImportingTimeFullPath) + { + return OngoingImports.Count > 0 ? OngoingImports.Peek () : (nonImportingTimeFullPath () ?? string.Empty); + } + + static readonly char [] item_target_sep = {';'}; + + internal static IEnumerable<T> GetAllItems<T> (Func<string,string> expandString, string include, string exclude, Func<string,T> creator, Func<string,ITaskItem> taskItemCreator, string directory, Action<T,string> assignRecurse, Func<ITaskItem,bool> isDuplicate) + { + var includes = expandString (include).Trim ().Split (item_target_sep, StringSplitOptions.RemoveEmptyEntries); + var excludes = expandString (exclude).Trim ().Split (item_target_sep, StringSplitOptions.RemoveEmptyEntries); + + if (includes.Length == 0) + yield break; + if (includes.Length == 1 && includes [0].IndexOf ('*') < 0 && excludes.Length == 0) { + // for most case - shortcut. + var item = creator (includes [0]); + yield return item; + } else { + var ds = new Microsoft.Build.BuildEngine.DirectoryScanner () { + BaseDirectory = new DirectoryInfo (directory), + Includes = includes.Where (s => !string.IsNullOrWhiteSpace (s)).Select (i => taskItemCreator (i)).ToArray (), + Excludes = excludes.Where (s => !string.IsNullOrWhiteSpace (s)).Select (e => taskItemCreator (e)).ToArray (), + }; + ds.Scan (); + foreach (var taskItem in ds.MatchedItems) { + if (isDuplicate (taskItem)) + continue; // skip duplicate + var item = creator (taskItem.ItemSpec); + string recurse = taskItem.GetMetadata ("RecursiveDir"); + assignRecurse (item, recurse); + yield return item; + } + } + } + + static readonly char [] path_sep = {Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar}; + + internal static string GetWellKnownMetadata (string name, string file, Func<string,string> getFullPath, string recursiveDir) + { + switch (name.ToLower (CultureInfo.InvariantCulture)) { + case "fullpath": + return getFullPath (file); + case "rootdir": + return Path.GetPathRoot (getFullPath (file)); + case "filename": + return Path.GetFileNameWithoutExtension (file); + case "extension": + return Path.GetExtension (file); + case "relativedir": + var idx = file.LastIndexOfAny (path_sep); + return idx < 0 ? string.Empty : file.Substring (0, idx + 1); + case "directory": + var fp = getFullPath (file); + return Path.GetDirectoryName (fp).Substring (Path.GetPathRoot (fp).Length); + case "recursivedir": + return recursiveDir; + case "identity": + return file; + case "modifiedtime": + return new FileInfo (getFullPath (file)).LastWriteTime.ToString ("yyyy-MM-dd HH:mm:ss.fffffff"); + case "createdtime": + return new FileInfo (getFullPath (file)).CreationTime.ToString ("yyyy-MM-dd HH:mm:ss.fffffff"); + case "accessedtime": + return new FileInfo (getFullPath (file)).LastAccessTime.ToString ("yyyy-MM-dd HH:mm:ss.fffffff"); + } + return null; + } + } } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ProjectCollectionChangedEventArgs.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ProjectCollectionChangedEventArgs.cs new file mode 100644 index 0000000000..a4e8ee90d7 --- /dev/null +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ProjectCollectionChangedEventArgs.cs @@ -0,0 +1,16 @@ +using System; +using Microsoft.Build.Construction; + +namespace Microsoft.Build.Evaluation +{ + public class ProjectCollectionChangedEventArgs : EventArgs + { + public ProjectCollectionChangedEventArgs (ProjectCollectionChangedState state) + { + State = state; + } + + public ProjectCollectionChangedState State { get; private set; } + } +} + diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ProjectCollectionChangedState.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ProjectCollectionChangedState.cs new file mode 100644 index 0000000000..683080f13e --- /dev/null +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ProjectCollectionChangedState.cs @@ -0,0 +1,19 @@ +using System; +using Microsoft.Build.Construction; + +namespace Microsoft.Build.Evaluation +{ + public enum ProjectCollectionChangedState + { + DefaultToolsVersion, + DisableMarkDirty, + GlobalProperties, + HostServices, + IsBuildEnabled, + Loggers, + OnlyLogCriticalEvents, + SkipEvaluation, + Toolsets + } +} + diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ProjectItem.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ProjectItem.cs index 393995e80c..cc187f9b94 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ProjectItem.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ProjectItem.cs @@ -4,9 +4,10 @@ // Author: // Leszek Ciesielski (skolima@gmail.com) // Rolf Bjarne Kvinge (rolf@xamarin.com) +// Atsushi Enomoto (atsushi@xamarin.com) // // (C) 2011 Leszek Ciesielski -// Copyright (C) 2011 Xamarin Inc. +// Copyright (C) 2011,2013 Xamarin Inc. // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the @@ -31,88 +32,126 @@ using System; using System.Collections.Generic; using System.Diagnostics; - +using System.Linq; using Microsoft.Build.Construction; +using System.IO; +using Microsoft.Build.Framework; namespace Microsoft.Build.Evaluation { - [DebuggerDisplay("{ItemType}={EvaluatedInclude} [{UnevaluatedInclude}] #DirectMetadata={DirectMetadataCount}")] - public class ProjectItem - { - internal ProjectItem (ProjectItemElement xml) - { - Xml = xml; - } - - public ProjectMetadata GetMetadata (string name) - { - throw new NotImplementedException (); - } - - public string GetMetadataValue (string name) - { - throw new NotImplementedException (); - } - - public bool HasMetadata (string name) - { - throw new NotImplementedException (); - } - - public bool RemoveMetadata (string name) - { - throw new NotImplementedException (); - } - - public void Rename (string name) - { - throw new NotImplementedException (); - } - - public void SetMetadataValue (string name, string unevaluatedValue) - { - throw new NotImplementedException (); - } - - public IEnumerable<ProjectMetadata> DirectMetadata { - get { throw new NotImplementedException (); } - } - - public int DirectMetadataCount { - get { throw new NotImplementedException (); } - } - - public string EvaluatedInclude { - get { throw new NotImplementedException (); } - } - - public bool IsImported { - get { throw new NotImplementedException (); } - } - - public string ItemType { - get { throw new NotImplementedException (); } - set { throw new NotImplementedException (); } - } - - public ICollection<ProjectMetadata> Metadata { - get { throw new NotImplementedException (); } - } - - public int MetadataCount { - get { throw new NotImplementedException (); } - } - - public Project Project { - get { throw new NotImplementedException (); } - } - - public string UnevaluatedInclude { - get { throw new NotImplementedException (); } - set { throw new NotImplementedException (); } - } - - public ProjectItemElement Xml { get; private set; } - - } + [DebuggerDisplay ("{ItemType}={EvaluatedInclude} [{UnevaluatedInclude}] #DirectMetadata={DirectMetadataCount}")] + public class ProjectItem + { + internal ProjectItem (Project project, ProjectItemElement xml, string evaluatedInclude) + { + this.project = project; + this.xml = xml; + if (project.ItemDefinitions.ContainsKey (ItemType)) + foreach (var md in project.ItemDefinitions [ItemType].Metadata) + metadata.Add (md); + foreach (var md in xml.Metadata) + metadata.Add (new ProjectMetadata (project, ItemType, metadata, m => metadata.Remove (m), md)); + this.evaluated_include = evaluatedInclude; + is_imported = project.ProjectCollection.OngoingImports.Any (); + } + + readonly Project project; + readonly ProjectItemElement xml; + readonly List<ProjectMetadata> metadata = new List<ProjectMetadata> (); + readonly bool is_imported; + readonly string evaluated_include; + + internal string RecursiveDir { get; set; } + + public ProjectMetadata GetMetadata (string name) + { + return metadata.FirstOrDefault (m => m.Name == name); + } + + public string GetMetadataValue (string name) + { + if (name == null) + throw new ArgumentNullException ("name"); + var wk = ProjectCollection.GetWellKnownMetadata (name, EvaluatedInclude, project.GetFullPath, RecursiveDir); + if (wk != null) + return wk; + var m = GetMetadata (name); + return m != null ? m.EvaluatedValue : string.Empty; + } + + public bool HasMetadata (string name) + { + return GetMetadata (name) != null; + } + + public bool RemoveMetadata (string name) + { + var m = GetMetadata (name); + if (m == null) + return false; + return metadata.Remove (m); + } + + public void Rename (string name) + { + throw new NotImplementedException (); + } + + public ProjectMetadata SetMetadataValue (string name, string unevaluatedValue) + { + // This has to do several tasks: + // - it cannot directly change Xml.Metadata because the ProjectItemElement might be shared + // among multiple ProjectItems. + // - hence it has to create another ProjectItemElement instance and add it to the project + // XML construction, with specific Include value that is assigned to this instance, and + // metadata values that are assigned to this instance. + throw new NotImplementedException (); + } + + public IEnumerable<ProjectMetadata> DirectMetadata { + get { + var list = new List<ProjectMetadata> (); + foreach (var xm in xml.Metadata) + yield return new ProjectMetadata (project, ItemType, list, p => list.Remove (p), xm); + } + } + + public int DirectMetadataCount { + get { return xml.Metadata.Count; } + } + + public string EvaluatedInclude { + get { return evaluated_include; } + } + + public bool IsImported { + get { return is_imported; } + } + + public string ItemType { + get { return Xml.ItemType; } + set { Xml.ItemType = value; } + } + + public ICollection<ProjectMetadata> Metadata { + get { return metadata; } + } + + public int MetadataCount { + get { return metadata.Count; } + } + + public Project Project { + get { return project; } + } + + public string UnevaluatedInclude { + get { return Xml.Include; } + set { Xml.Include = value; } + } + + public ProjectItemElement Xml { + get { return xml; } + } + } } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ProjectItemDefinition.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ProjectItemDefinition.cs index 13ef541bc6..faa880603b 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ProjectItemDefinition.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ProjectItemDefinition.cs @@ -2,8 +2,9 @@ // // Author: // Rolf Bjarne Kvinge (rolf@xamarin.com) +// Atsushi Enomoto (atsushi@xamarin.com) // -// Copyright (C) 2011 Xamarin Inc. +// Copyright (C) 2011,2013 Xamarin Inc. // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the @@ -26,15 +27,43 @@ // using System; +using System.Collections.Generic; +using Microsoft.Build.Construction; namespace Microsoft.Build.Evaluation { - public class ProjectItemDefinition - { - private ProjectItemDefinition () - { - throw new NotImplementedException (); - } - } -} + public class ProjectItemDefinition + { + internal ProjectItemDefinition (Project project, string itemType) + { + this.project = project; + this.item_type = itemType; + } + + Project project; + string item_type; + List<ProjectMetadata> metadata = new List<ProjectMetadata> (); + + public string ItemType { + get { return item_type; } + } + public IEnumerable<ProjectMetadata> Metadata { + get { return metadata; } + } + + public int MetadataCount { + get { return metadata.Count; } + } + + public Project Project { + get { return project; } + } + + internal void AddItems (ProjectItemDefinitionElement xml) + { + foreach (var item in xml.Metadata) + metadata.Add (new ProjectMetadata (project, ItemType, metadata, m => metadata.Remove (m), item)); + } + } +} diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ProjectMetadata.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ProjectMetadata.cs index 45001f61a2..880a0e336b 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ProjectMetadata.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ProjectMetadata.cs @@ -2,8 +2,9 @@ // // Author: // Rolf Bjarne Kvinge (rolf@xamarin.com) +// Atsushi Enomoto (atsushi@xamarin.com) // -// Copyright (C) 2011 Xamarin Inc. +// Copyright (C) 2011,2013 Xamarin Inc. // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the @@ -26,49 +27,62 @@ // using Microsoft.Build.Construction; - using System; +using System.Collections.Generic; +using System.Linq; namespace Microsoft.Build.Evaluation { - public class ProjectMetadata - { - private ProjectMetadata () - { - throw new NotImplementedException (); - } + public class ProjectMetadata + { + internal ProjectMetadata (Project project, string itemType, IEnumerable<ProjectMetadata> existingMetadata, Action<ProjectMetadata> remover, ProjectMetadataElement xml) + { + this.xml = xml; + this.project = project; + item_type = itemType; + predecessor = existingMetadata.FirstOrDefault (m => m.Name == xml.Name); + if (predecessor != null) + remover (predecessor); + is_imported = Project.ProjectCollection.OngoingImports.Any (); + } + + readonly Project project; + readonly string item_type; + readonly ProjectMetadataElement xml; + readonly ProjectMetadata predecessor; + readonly bool is_imported; - public string EvaluatedValue { - get { throw new NotImplementedException (); } - } + public string EvaluatedValue { + get { return project.ExpandString (xml.Value); } + } - public bool IsImported { - get { throw new NotImplementedException (); } - } + public bool IsImported { + get { return is_imported; } + } - public string ItemType { - get { throw new NotImplementedException (); } - } + public string ItemType { + get { return item_type; } + } - public string Name { - get { throw new NotImplementedException (); } - } + public string Name { + get { return xml.Name; } + } - public ProjectMetadata Predecessor { - get { throw new NotImplementedException (); } - } + public ProjectMetadata Predecessor { + get { return predecessor; } + } - public Project Project { - get { throw new NotImplementedException (); } - } + public Project Project { + get { return project; } + } - public string UnevaluatedValue { - get { throw new NotImplementedException (); } - } + public string UnevaluatedValue { + get { return xml.Value; } + } - public ProjectMetadataElement Xml { - get { throw new NotImplementedException (); } - } - } + public ProjectMetadataElement Xml { + get { return xml; } + } + } } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ProjectProperty.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ProjectProperty.cs index e9d557f3e0..757b40f3ce 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ProjectProperty.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ProjectProperty.cs @@ -2,8 +2,9 @@ // // Author: // Rolf Bjarne Kvinge (rolf@xamarin.com) +// Atsushi Enomoto (atsushi@xamarin.com) // -// Copyright (C) 2011 Xamarin Inc. +// Copyright (C) 2011,2013 Xamarin Inc. // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the @@ -26,7 +27,12 @@ // using System; +using System.Linq; using Microsoft.Build.Construction; +using Microsoft.Build.Internal; +using System.Collections.Generic; +using System.Reflection; +using System.IO; namespace Microsoft.Build.Evaluation { @@ -34,39 +40,249 @@ namespace Microsoft.Build.Evaluation // members are abstract and had been there since 4.0. // I take this as doc bug, as non-abstract to abstract is a // breaking change and I'd rather believe API designer's sanity. - public abstract class ProjectProperty - { - internal ProjectProperty () // hide default ctor + public abstract class ProjectProperty + { + internal ProjectProperty (Project project) // hide default ctor { + Project = project; } - public string EvaluatedValue { - get { - throw new NotImplementedException (); - } - } + public string EvaluatedValue { + get { return InternalEvaluatedValue; } + } public abstract bool IsEnvironmentProperty { get; } + public abstract bool IsGlobalProperty { get; } + + [MonoTODO] public abstract bool IsImported { get; } + public abstract bool IsReservedProperty { get; } - public string Name { - get { - throw new NotImplementedException (); - } + internal virtual bool IsWellKnownProperty { + get { return false; } } + public abstract string Name { get; } + public abstract ProjectProperty Predecessor { get; } - public Project Project { + public Project Project { get; private set; } + + public abstract string UnevaluatedValue { get; set; } + + public abstract ProjectPropertyElement Xml { get; } + + internal abstract string InternalEvaluatedValue { get; } + } + + // copy from MS.Build.Engine/BuildProperty.cs + internal enum PropertyType { + Reserved, + Global, + Normal, + Environment + } + + internal abstract class BaseProjectProperty : ProjectProperty + { + public BaseProjectProperty (Project project, PropertyType propertyType, string name) + : base (project) + { + property_type = propertyType; + this.name = name; + predecessor = project.Properties.FirstOrDefault (p => p.Name == name); + if (predecessor != null) + project.RemoveProperty (predecessor); + } + + PropertyType property_type; + + readonly string name; + public override string Name { + get { return name; } + } + + public override bool IsEnvironmentProperty { + get { return property_type == PropertyType.Environment; } + } + public override bool IsGlobalProperty { + get { return property_type == PropertyType.Global; } + } + public override bool IsImported { + get { return false; } + } + public override bool IsReservedProperty { + get { return property_type == PropertyType.Reserved; } + } + readonly ProjectProperty predecessor; + public override ProjectProperty Predecessor { + get { return predecessor; } + } + } + + internal abstract class ImmutableProjectProperty : BaseProjectProperty + { + public ImmutableProjectProperty (Project project, PropertyType propertyType, string name) + : base (project, propertyType, name) + { + } + + internal override string InternalEvaluatedValue { + get { return UnevaluatedValue; } + } + } + + internal abstract class MutableProjectProperty : BaseProjectProperty + { + public MutableProjectProperty (Project project, PropertyType propertyType, string name) + : base (project, propertyType, name) + { + } + + string evaluated_value; // see UpdateEvaluatedValue(). + internal void UpdateEvaluatedValue () + { + evaluated_value = Project.ExpandString (UnevaluatedValue); + } + + internal override string InternalEvaluatedValue { + get { return evaluated_value; } + } + } + + internal class XmlProjectProperty : MutableProjectProperty + { + public XmlProjectProperty (Project project, ProjectPropertyElement xml, PropertyType propertyType, bool isImported) + : base (project, propertyType, xml.Name) + { + this.xml = xml; + this.is_imported = isImported; + UpdateEvaluatedValue (); + } + + readonly ProjectPropertyElement xml; + readonly bool is_imported; + + public override bool IsImported { + get { return is_imported; } + } + + public override string UnevaluatedValue { + get { return xml.Value; } + set { xml.Value = value; } + } + + public override ProjectPropertyElement Xml { + get { return xml; } + } + } + + internal class EnvironmentProjectProperty : ImmutableProjectProperty + { + static string extensions_path; + internal static string DefaultExtensionsPath { get { - throw new NotImplementedException (); + if (extensions_path == null) { + // NOTE: code from mcs/tools/gacutil/driver.cs + PropertyInfo gac = typeof (System.Environment).GetProperty ( + "GacPath", BindingFlags.Static | BindingFlags.NonPublic); + + if (gac != null) { + MethodInfo get_gac = gac.GetGetMethod (true); + string gac_path = (string) get_gac.Invoke (null, null); + extensions_path = Path.GetFullPath (Path.Combine ( + gac_path, Path.Combine ("..", "xbuild"))); + } + } + return extensions_path; } } + + public EnvironmentProjectProperty (Project project, string name, string value, bool wellknown = false) + : base (project, PropertyType.Environment, name) + { + this.value = value; + this.wellknown = wellknown; + } + + readonly string value; + readonly bool wellknown; - public abstract string UnevaluatedValue { get; set; } - public abstract ProjectPropertyElement Xml { get; } - } -} + internal override bool IsWellKnownProperty { + get { return wellknown; } + } + // It can override possible another environment vairable property BUT never gives Predecessor. + public override ProjectProperty Predecessor { + get { return null; } + } + + public override string UnevaluatedValue { + get { return value; } + set { throw new InvalidOperationException (string.Format ("You cannot change value of environment property '{0}'.", Name)); } + } + public override ProjectPropertyElement Xml { + get { return null; } + } + } + + internal class GlobalProjectProperty : ImmutableProjectProperty + { + public GlobalProjectProperty (Project project, string name, string value) + : base (project, PropertyType.Global, name) + { + this.value = value; + } + + readonly string value; + + public override string UnevaluatedValue { + get { return value; } + set { throw new InvalidOperationException (string.Format ("You cannot change value of global property '{0}'.", Name)); } + } + public override ProjectPropertyElement Xml { + get { return null; } + } + } + + internal class ManuallyAddedProjectProperty : MutableProjectProperty + { + public ManuallyAddedProjectProperty (Project project, string name, string value) + : base (project, PropertyType.Normal, name) + { + this.UnevaluatedValue = value; + } + + public override string UnevaluatedValue { get; set; } + + public override ProjectPropertyElement Xml { + get { return null; } + } + } + + internal class ReservedProjectProperty : ImmutableProjectProperty + { + public ReservedProjectProperty (Project project, string name, Func<string> value) + : base (project, PropertyType.Reserved, name) + { + this.value = value; + } + + // make sure it does not give access to any possible attempted overrrides. + public override ProjectProperty Predecessor { + get { return null; } + } + + readonly Func<string> value; + public override string UnevaluatedValue { + get { return value (); } + set { throw new InvalidOperationException (string.Format ("You cannot change value of reserved property '{0}'.", Name)); } + } + + public override ProjectPropertyElement Xml { + get { return null; } + } + } +} diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Logging/ColorSetter.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ProjectXmlChangedEventArgs.cs index 7e510e7bbf..a0497a505a 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Logging/ColorSetter.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ProjectXmlChangedEventArgs.cs @@ -1,9 +1,10 @@ -// ColorSetter.cs +// +// ProjectXmlChangedEventArgs.cs // // Author: -// Rolf Bjarne Kvinge (rolf@xamarin.com) +// Atsushi Enomoto <atsushi@xamarin.com> // -// Copyright (C) 2011 Xamarin Inc. +// Copyright (C) 2013 Xamarin Inc. // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the @@ -15,7 +16,7 @@ // // 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 @@ -25,8 +26,20 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -namespace Microsoft.Build.Logging +using Microsoft.Build.Construction; +using System; +using System.Linq; + +namespace Microsoft.Build.Evaluation { - public delegate void ColorSetter (System.ConsoleColor color); + public class ProjectXmlChangedEventArgs : EventArgs + { + internal ProjectXmlChangedEventArgs (ProjectRootElement projectXml, string reason) + { + ProjectXml = projectXml; + Reason = reason; + } + public ProjectRootElement ProjectXml { get; private set; } + public string Reason { get; private set; } + } } - diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ResolvedImport.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ResolvedImport.cs index 1e82c01a4c..8a5bed7437 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ResolvedImport.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ResolvedImport.cs @@ -26,24 +26,35 @@ // using Microsoft.Build.Construction; - using System; namespace Microsoft.Build.Evaluation { - [System.Runtime.InteropServices.StructLayout (System.Runtime.InteropServices.LayoutKind.Sequential)] - public struct ResolvedImport - { - private ProjectImportElement import; - private ProjectRootElement root; + [System.Runtime.InteropServices.StructLayout (System.Runtime.InteropServices.LayoutKind.Sequential)] + public struct ResolvedImport + { + internal ResolvedImport (ProjectImportElement import, ProjectRootElement root, bool isImported) + { + this.import = import; + this.root = root; + this.imported = isImported; + } + + readonly ProjectImportElement import; + readonly ProjectRootElement root; + readonly bool imported; - public ProjectImportElement ImportingElement { - get { return import; } - } + public ProjectImportElement ImportingElement { + get { return import; } + } - public ProjectRootElement ImportedProject { - get { return root; } - } - } + public ProjectRootElement ImportedProject { + get { return root; } + } + + public bool IsImported { + get { return imported; } + } + } } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/SubToolset.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/SubToolset.cs new file mode 100644 index 0000000000..0b4ad39224 --- /dev/null +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/SubToolset.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using Microsoft.Build.Execution; + +namespace Microsoft.Build.Evaluation +{ + #if NET_4_5 + public + #endif + class SubToolset + { + internal SubToolset (IDictionary<string, ProjectPropertyInstance> properties, string subToolsetVersion) + { + Properties = properties; + SubToolsetVersion = subToolsetVersion; + } + + public IDictionary<string, ProjectPropertyInstance> Properties { get; private set; } + public string SubToolsetVersion { get; private set; } + } +} + diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/Toolset.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/Toolset.cs index 9da377be35..b0182c53bb 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/Toolset.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/Toolset.cs @@ -2,8 +2,9 @@ // // Author: // Rolf Bjarne Kvinge (rolf@xamarin.com) +// Atsushi Enomoto (atsushi@xamarin.com) // -// Copyright (C) 2011 Xamarin Inc. +// Copyright (C) 2011,2013 Xamarin Inc. // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the @@ -27,37 +28,54 @@ using System; using System.Collections.Generic; - +using System.Linq; using Microsoft.Build.Execution; namespace Microsoft.Build.Evaluation { - public class Toolset - { - public Toolset (string toolsVersion, string toolsPath, - ProjectCollection projectCollection, string msbuildOverrideTasksPath) - { - throw new NotImplementedException (); - } - - public Toolset (string toolsVersion, string toolsPath, - IDictionary<string, string> buildProperties, ProjectCollection projectCollection, - string msbuildOverrideTasksPath) - { - throw new NotImplementedException (); - } - - public IDictionary<string, ProjectPropertyInstance> Properties { - get { throw new NotImplementedException (); } - } - - public string ToolsPath { - get { throw new NotImplementedException (); } - } - - public string ToolsVersion { - get { throw new NotImplementedException (); } - } - } + public class Toolset + { + public Toolset (string toolsVersion, string toolsPath, + ProjectCollection projectCollection, string msbuildOverrideTasksPath) + : this (toolsVersion, toolsPath, null, projectCollection, msbuildOverrideTasksPath) + { + } + + public Toolset (string toolsVersion, string toolsPath, + IDictionary<string, string> buildProperties, ProjectCollection projectCollection, + string msbuildOverrideTasksPath) + : this (toolsVersion, toolsPath, buildProperties, projectCollection, null, msbuildOverrideTasksPath) + { + } + +#if NET_4_5 + public +#endif + Toolset (string toolsVersion, string toolsPath, IDictionary<string, string> buildProperties, + ProjectCollection projectCollection, IDictionary<string, SubToolset> subToolsets, + string msbuildOverrideTasksPath) + { + ToolsVersion = toolsVersion; + ToolsPath = toolsPath; + Properties = + buildProperties == null ? + new Dictionary<string, ProjectPropertyInstance> () : + buildProperties.Select (p => new ProjectPropertyInstance (p.Key, true, p.Value)).ToDictionary (e => e.Name); +#if NET_4_5 + SubToolsets = subToolsets ?? new Dictionary<string, SubToolset> (); +#endif + } + +#if NET_4_5 + public string DefaultSubToolsetVersion { get; private set; } + public IDictionary<string, SubToolset> SubToolsets { get; private set; } +#endif + + public IDictionary<string, ProjectPropertyInstance> Properties { get; private set; } + + public string ToolsPath { get; private set; } + + public string ToolsVersion { get; private set; } + } } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Exceptions/BuildAbortedException.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Exceptions/BuildAbortedException.cs new file mode 100644 index 0000000000..70a2969260 --- /dev/null +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Exceptions/BuildAbortedException.cs @@ -0,0 +1,43 @@ +using System; +using System.Runtime.Serialization; + +namespace Microsoft.Build.Exceptions +{ + public class BuildAbortedException : Exception + { + public BuildAbortedException () + : this ("Build aborted") + { + } + + public BuildAbortedException (string message) + : base (message) + { + } + + public BuildAbortedException (string message, Exception innerException) + : base (message, innerException) + { + } + protected BuildAbortedException (SerializationInfo info, StreamingContext context) + : base (info, context) + { + ErrorCode = info.GetString ("errorCode"); + } + + internal BuildAbortedException (string message, string errorCode) + : base (message + " error code: " + errorCode) + { + ErrorCode = errorCode; + } + + public string ErrorCode { get; private set; } + + public override void GetObjectData (SerializationInfo info, StreamingContext context) + { + base.GetObjectData (info, context); + info.AddValue ("errorCode", ErrorCode); + } + } +} + diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Exceptions/InternalLoggerException.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Exceptions/InternalLoggerException.cs new file mode 100644 index 0000000000..3aecdfdbb0 --- /dev/null +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Exceptions/InternalLoggerException.cs @@ -0,0 +1,57 @@ +using System; +using System.Runtime.Serialization; +using Microsoft.Build.Framework; + +namespace Microsoft.Build.Exceptions +{ + public class InternalLoggerException : Exception + { + public InternalLoggerException () + : this ("Build aborted") + { + } + + public InternalLoggerException (string message) + : base (message) + { + } + + public InternalLoggerException (string message, Exception innerException) + : base (message, innerException) + { + } + + internal InternalLoggerException (string message, Exception innerException, BuildEventArgs buildEventArgs, string errorCode, string helpKeyword, bool initializationException) + : base (message, innerException) + { + BuildEventArgs = buildEventArgs; + ErrorCode = errorCode; + HelpKeyword = helpKeyword; + InitializationException = initializationException; + } + + internal InternalLoggerException (SerializationInfo info, StreamingContext context) + : base (info, context) + { + BuildEventArgs = (BuildEventArgs) info.GetValue ("buildEventArgs", typeof (BuildEventArgs)); + ErrorCode = info.GetString ("errorCode"); + HelpKeyword = info.GetString ("helpKeyword"); + InitializationException = info.GetBoolean ("initializationException"); + } + + public BuildEventArgs BuildEventArgs { get; private set; } + public string ErrorCode { get; private set; } + public string HelpKeyword { get; private set; } + public bool InitializationException { get; private set; } + + public override void GetObjectData (SerializationInfo info, StreamingContext context) + { + base.GetObjectData (info, context); + info.AddValue ("buildEventArgs", BuildEventArgs); + info.AddValue ("errorCode", ErrorCode); + info.AddValue ("helpKeyword", HelpKeyword); + info.AddValue ("initializationException", InitializationException); + } + } +} + diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Exceptions/InvalidProjectFileException.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Exceptions/InvalidProjectFileException.cs index ea2a51dc40..ad05ff6403 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Exceptions/InvalidProjectFileException.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Exceptions/InvalidProjectFileException.cs @@ -28,6 +28,8 @@ using System; using System.Runtime.Serialization; +using Microsoft.Build.Construction; +using Microsoft.Build.Internal.Expressions; namespace Microsoft.Build.Exceptions { @@ -57,10 +59,31 @@ namespace Microsoft.Build.Exceptions : base(message, innerException) { } + internal InvalidProjectFileException (ILocation start, string message, + string errorSubcategory = null, string errorCode = null, string helpKeyword = null) + : this (start != null ? start.File : null, 0, start != null ? start.Column : 0, 0, 0, message, errorSubcategory, errorCode, helpKeyword) + { + } + internal InvalidProjectFileException (ElementLocation start, ElementLocation end, string message, + string errorSubcategory = null, string errorCode = null, string helpKeyword = null) + : this (start != null ? start.File : null, start != null ? start.Line : 0, start != null ? start.Column : 0, + end != null ? end.Line : 0, end != null ? end.Column : 0, + message, errorSubcategory, errorCode, helpKeyword) + { + } public InvalidProjectFileException (string projectFile, int lineNumber, int columnNumber, int endLineNumber, int endColumnNumber, string message, string errorSubcategory, string errorCode, string helpKeyword) + : base(message) { + ProjectFile = projectFile; + LineNumber = lineNumber; + ColumnNumber = columnNumber; + EndLineNumber = endLineNumber; + EndColumnNumber = endColumnNumber; + ErrorSubcategory = errorSubcategory; + ErrorCode = errorCode; + HelpKeyword = helpKeyword; } public override void GetObjectData (SerializationInfo info, StreamingContext context) { @@ -87,8 +110,15 @@ namespace Microsoft.Build.Exceptions public string HelpKeyword { get; private set; } public int LineNumber { get; private set; } public override string Message { - get { return ProjectFile == null ? base.Message : base.Message + " " + ProjectFile; } + get { return ProjectFile == null ? base.Message : base.Message + " " + GetLocation (); } } public string ProjectFile { get; private set; } + + string GetLocation () + { + string start = LineNumber == 0 ? string.Empty : ColumnNumber > 0 ? string.Format ("{0},{1}", LineNumber, ColumnNumber) : string.Format ("{0}", LineNumber); + string end = EndLineNumber == 0 ? string.Empty : EndColumnNumber > 0 ? string.Format (" - {0},{1}", EndLineNumber, EndColumnNumber) : string.Format (" - {0}", EndLineNumber); + return LineNumber == 0 ? ProjectFile : String.Format (" at: {0} ({1}{2})", ProjectFile, start, end); + } } } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Exceptions/InvalidToolsetDefinitionException.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Exceptions/InvalidToolsetDefinitionException.cs new file mode 100644 index 0000000000..b518d182ac --- /dev/null +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Exceptions/InvalidToolsetDefinitionException.cs @@ -0,0 +1,42 @@ +using System; +using System.Runtime.Serialization; + +namespace Microsoft.Build.Exceptions +{ + public class InvalidToolsetDefinitionException : Exception + { + public InvalidToolsetDefinitionException () + : this ("Invalid toolset definition") + { + } + + public InvalidToolsetDefinitionException (string message) + : base (message) + { + } + + public InvalidToolsetDefinitionException (string message, Exception innerException) + : base (message, innerException) + { + } + protected InvalidToolsetDefinitionException (SerializationInfo info, StreamingContext context) + : base (info, context) + { + ErrorCode = info.GetString ("errorCode"); + } + + internal InvalidToolsetDefinitionException (string message, string errorCode) + : base (message + " error code: " + errorCode) + { + ErrorCode = errorCode; + } + + public string ErrorCode { get; private set; } + + public override void GetObjectData (SerializationInfo info, StreamingContext context) + { + base.GetObjectData (info, context); + info.AddValue ("errorCode", ErrorCode); + } + } +} diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/BuildManager.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/BuildManager.cs index 3b4384b2f6..0c179afa48 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/BuildManager.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/BuildManager.cs @@ -2,8 +2,9 @@ // // Author: // Rolf Bjarne Kvinge (rolf@xamarin.com) +// Atsushi Enomoto (atsushi@xamarin.com) // -// Copyright (C) 2011 Xamarin Inc. +// Copyright (C) 2011,2013 Xamarin Inc. // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the @@ -26,66 +27,143 @@ // using Microsoft.Build.Evaluation; - using System; +using System.Collections.Generic; +using System.Threading; +using Microsoft.Build.Internal; +using System.Linq; namespace Microsoft.Build.Execution { - public class BuildManager - { - public BuildManager () - { - throw new NotImplementedException (); - } + public class BuildManager + { + static BuildManager default_manager = new BuildManager (); - public BuildManager (string hostName) - { - throw new NotImplementedException (); - } + public static BuildManager DefaultBuildManager { + get { return default_manager; } + } + + public BuildManager () + { + } - public void BeginBuild (BuildParameters parameters) - { - throw new NotImplementedException (); - } + public BuildManager (string hostName) + { + throw new NotImplementedException (); + } + + public void Dispose () + { + WaitHandle.WaitAll (submissions.Select (s => s.WaitHandle).ToArray ()); + BuildNodeManager.Stop (); + } - public BuildResult Build (BuildParameters parameters, BuildRequestData requestData) - { - throw new NotImplementedException (); - } + ~BuildManager () + { + // maybe processes created by out-of-process nodes should be signaled. + BuildNodeManager.Stop (); + } - public BuildResult BuildRequest (BuildRequestData requestData) - { - throw new NotImplementedException (); - } + readonly List<BuildSubmission> submissions = new List<BuildSubmission> (); + + BuildParameters ongoing_build_parameters; + + internal BuildParameters OngoingBuildParameters { + get { return ongoing_build_parameters; } + } - public void CancelAllSubmissions () - { - throw new NotImplementedException (); - } + public void BeginBuild (BuildParameters parameters) + { + if (ongoing_build_parameters != null) + throw new InvalidOperationException ("There is already ongoing build"); + ongoing_build_parameters = parameters.Clone (); + } - public void EndBuild () - { - throw new NotImplementedException (); - } + public BuildResult Build (BuildParameters parameters, BuildRequestData requestData) + { + BeginBuild (parameters); + var ret = BuildRequest (requestData); + EndBuild (); + return ret; + } - public ProjectInstance GetProjectInstanceForBuild (Project project) - { - throw new NotImplementedException (); - } + public BuildResult BuildRequest (BuildRequestData requestData) + { + var sub = PendBuildRequest (requestData); + sub.Execute (); + return sub.BuildResult; + } + + public void CancelAllSubmissions () + { + foreach (var sub in submissions) { + try { + if (!sub.IsCompleted) + sub.Cancel (); + } catch (InvalidOperationException) { + // some submissions could be already done during this iteration. Ignore that. + } + } + submissions.Clear (); + } - public BuildSubmission PendBuildRequest (BuildRequestData requestData) - { - throw new NotImplementedException (); - } + public void EndBuild () + { + if (ongoing_build_parameters == null) + throw new InvalidOperationException ("Build has not started"); + if (submissions.Count > 0) + WaitHandle.WaitAll (submissions.Select (s => s.WaitHandle).ToArray ()); + BuildNodeManager.Stop (); + ongoing_build_parameters = null; + } + + Dictionary<Project,ProjectInstance> instances = new Dictionary<Project, ProjectInstance> (); - public void ResetCaches () - { - throw new NotImplementedException (); - } + public ProjectInstance GetProjectInstanceForBuild (Project project) + { + if (project == null) + throw new ArgumentNullException ("project"); + if (project.FullPath == null) + throw new ArgumentNullException ("project", "FullPath parameter in the project cannot be null."); + if (project.FullPath == string.Empty) + throw new ArgumentException ("FullPath parameter in the project cannot be empty.", "project"); + // other than that, any invalid path character is accepted... + + return GetProjectInstanceForBuildInternal (project); + } + + internal ProjectInstance GetProjectInstanceForBuildInternal (Project project) + { + if (!instances.ContainsKey (project)) + instances [project] = project.CreateProjectInstance (); + return instances [project]; + } - public static BuildManager DefaultBuildManager { - get { throw new NotImplementedException (); } - } - } -} + public BuildSubmission PendBuildRequest (BuildRequestData requestData) + { + if (ongoing_build_parameters == null) + throw new InvalidOperationException ("This method cannot be called before calling BeginBuild method."); + var sub = new BuildSubmission (this, requestData); + submissions.Add (sub); + return sub; + } + public void ResetCaches () + { + if (OngoingBuildParameters != null) + throw new InvalidOperationException ("Cannot reset caches while builds are in progress."); + + BuildNodeManager.ResetCaches (); + } + + BuildNodeManager build_node_manager; + + internal BuildNodeManager BuildNodeManager { + get { + if (build_node_manager == null) + build_node_manager = new BuildNodeManager (this); + return build_node_manager; + } + } + } +} diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/BuildParameters.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/BuildParameters.cs index 26944910f9..61f8393cda 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/BuildParameters.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/BuildParameters.cs @@ -2,8 +2,9 @@ // // Author: // Rolf Bjarne Kvinge (rolf@xamarin.com) +// Atsushi Enomoto (atsushi@xamarin.com) // -// Copyright (C) 2011 Xamarin Inc. +// Copyright (C) 2011,2013 Xamarin Inc. // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the @@ -28,132 +29,128 @@ using Microsoft.Build.Evaluation; using Microsoft.Build.Framework; using Microsoft.Build.Logging; - using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Threading; +using System.Collections; namespace Microsoft.Build.Execution { - public class BuildParameters - { - public BuildParameters () - { - throw new NotImplementedException (); - } - - public BuildParameters (ProjectCollection projectCollection) - { - throw new NotImplementedException (); - } - - public BuildParameters Clone () - { - throw new NotImplementedException (); - } - - public Toolset GetToolset (string toolsVersion) - { - throw new NotImplementedException (); - } - - public ThreadPriority BuildThreadPriority { - get { throw new NotImplementedException (); } - set { throw new NotImplementedException (); } - } - - public CultureInfo Culture { - get { throw new NotImplementedException (); } - set { throw new NotImplementedException (); } - } - - public string DefaultToolsVersion { - get { throw new NotImplementedException (); } - set { throw new NotImplementedException (); } - } - - public bool DetailedSummary { - get { throw new NotImplementedException (); } - set { throw new NotImplementedException (); } - } - - public bool EnableNodeReuse { - get { throw new NotImplementedException (); } - set { throw new NotImplementedException (); } - } - - public IDictionary<string, string> EnvironmentProperties { - get { throw new NotImplementedException (); } - } - - public IEnumerable<ForwardingLoggerRecord> ForwardingLoggers { - get { throw new NotImplementedException (); } - set { throw new NotImplementedException (); } - } - - public IDictionary<string, string> GlobalProperties { - get { throw new NotImplementedException (); } - set { throw new NotImplementedException (); } - } - - public HostServices HostServices { - get { throw new NotImplementedException (); } - set { throw new NotImplementedException (); } - } - - public bool LegacyThreadingSemantics { get; set; } - - public IEnumerable<ILogger> Loggers { - get { throw new NotImplementedException (); } - set { throw new NotImplementedException (); } - } - - public int MaxNodeCount { - get { throw new NotImplementedException (); } - set { throw new NotImplementedException (); } - } - - public int MemoryUseLimit { - get { throw new NotImplementedException (); } - set { throw new NotImplementedException (); } - } - - public string NodeExeLocation { - get { throw new NotImplementedException (); } - set { throw new NotImplementedException (); } - } - - public bool OnlyLogCriticalEvents { - get { throw new NotImplementedException (); } - set { throw new NotImplementedException (); } - } - - public bool ResetCaches { get; set; } - - public bool SaveOperatingEnvironment { - get { throw new NotImplementedException (); } - set { throw new NotImplementedException (); } - } - - public ToolsetDefinitionLocations ToolsetDefinitionLocations { - get { throw new NotImplementedException (); } - set { throw new NotImplementedException (); } - } - - public ICollection<Toolset> Toolsets { - get { throw new NotImplementedException (); } - } - - public CultureInfo UICulture { - get { throw new NotImplementedException (); } - set { throw new NotImplementedException (); } - } - - public bool UseSynchronousLogging { - get { throw new NotImplementedException (); } - set { throw new NotImplementedException (); } - } - } + public class BuildParameters + { + public BuildParameters () + : this (new ProjectCollection ()) + { + } + + public BuildParameters (ProjectCollection projectCollection) + { + if (projectCollection == null) + throw new ArgumentNullException ("projectCollection"); + projects = projectCollection; + + EnableNodeReuse = true; + Culture = CultureInfo.CurrentCulture; + UICulture = CultureInfo.CurrentUICulture; + MaxNodeCount = projectCollection.MaxNodeCount; + + // these properties are copied, while some members (such as Loggers) are not. + this.DefaultToolsVersion = projectCollection.DefaultToolsVersion; + this.ToolsetDefinitionLocations = projectCollection.ToolsetLocations; + this.GlobalProperties = projectCollection.GlobalProperties; + environment_properties = new Dictionary<string,string> (); + foreach (DictionaryEntry p in Environment.GetEnvironmentVariables ()) + environment_properties [(string) p.Key] = (string) p.Value; + } + + readonly ProjectCollection projects; + Dictionary<string,string> environment_properties; + + internal ProjectCollection ProjectCollection { + get { return projects; } + } + + public BuildParameters Clone () + { + var ret = (BuildParameters) MemberwiseClone (); + ret.ForwardingLoggers = ForwardingLoggers == null ? null : ForwardingLoggers.ToArray (); + ret.GlobalProperties = GlobalProperties == null ? null : GlobalProperties.ToDictionary (p => p.Key, p => p.Value); + ret.Loggers = Loggers == null ? null : new List<ILogger> (Loggers); + ret.environment_properties = new Dictionary<string, string> (environment_properties); + return ret; + } + + public Toolset GetToolset (string toolsVersion) + { + // can return null. + return projects.Toolsets.FirstOrDefault (t => t.ToolsVersion == toolsVersion); + } + + [MonoTODO] + public ThreadPriority BuildThreadPriority { get; set; } + + [MonoTODO] + public CultureInfo Culture { get; set; } + + public string DefaultToolsVersion { get; set; } + + [MonoTODO] + public bool DetailedSummary { get; set; } + + public bool EnableNodeReuse { get; set; } + + [MonoTODO] + public IDictionary<string, string> EnvironmentProperties { + get { return environment_properties; } + } + + [MonoTODO] + public IEnumerable<ForwardingLoggerRecord> ForwardingLoggers { get; set; } + + [MonoTODO] + public IDictionary<string, string> GlobalProperties { get; set; } + + public HostServices HostServices { get; set; } + + [MonoTODO] + public bool LegacyThreadingSemantics { get; set; } + + [MonoTODO] + public IEnumerable<ILogger> Loggers { get; set; } + + [MonoTODO] + public int MaxNodeCount { get; set; } + + [MonoTODO] + public int MemoryUseLimit { get; set; } + + [MonoTODO] + public string NodeExeLocation { get; set; } + + [MonoTODO] + public bool OnlyLogCriticalEvents { get; set; } + + [MonoTODO] + public bool ResetCaches { get; set; } + + [MonoTODO] + public bool SaveOperatingEnvironment { get; set; } + + [MonoTODO] + public ToolsetDefinitionLocations ToolsetDefinitionLocations { get; set; } + + [MonoTODO] + public ICollection<Toolset> Toolsets { + get { return projects.Toolsets; } + } + + [MonoTODO] + public CultureInfo UICulture { get; set; } + + [MonoTODO] + public bool UseSynchronousLogging { get; set; } + } } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/BuildRequestData.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/BuildRequestData.cs index 2380077e51..bd5edffd7c 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/BuildRequestData.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/BuildRequestData.cs @@ -3,8 +3,9 @@ // // Author: // Rolf Bjarne Kvinge (rolf@xamarin.com) +// Atsushi Enomoto (atsushi@xamarin.com) // -// Copyright (C) 2011 Xamarin Inc. +// Copyright (C) 2011,2013 Xamarin Inc. // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the @@ -27,50 +28,69 @@ // using System; +using System.Linq; using System.Collections.Generic; namespace Microsoft.Build.Execution { - public class BuildRequestData - { - public BuildRequestData (ProjectInstance projectInstance, string[] targetsToBuild) - : this (projectInstance, targetsToBuild, null, BuildRequestDataFlags.None) - { - } + public class BuildRequestData + { + public BuildRequestData (ProjectInstance projectInstance, string[] targetsToBuild) + : this (projectInstance, targetsToBuild, null, BuildRequestDataFlags.None) + { + } - public BuildRequestData (ProjectInstance projectInstance, string[] targetsToBuild, HostServices hostServices) - : this (projectInstance, targetsToBuild, hostServices, BuildRequestDataFlags.None) - { - } + public BuildRequestData (ProjectInstance projectInstance, string[] targetsToBuild, HostServices hostServices) + : this (projectInstance, targetsToBuild, hostServices, BuildRequestDataFlags.None) + { + } - public BuildRequestData (ProjectInstance projectInstance, string[] targetsToBuild, HostServices hostServices, - BuildRequestDataFlags flags) - { - throw new NotImplementedException (); - } + public BuildRequestData (ProjectInstance projectInstance, string[] targetsToBuild, HostServices hostServices, + BuildRequestDataFlags flags) + { + ProjectInstance = projectInstance; + TargetNames = targetsToBuild; + HostServices = hostServices; + Flags = flags; + } - public BuildRequestData (string projectFullPath, IDictionary<string, string> globalProperties, - string toolsVersion, string[] targetsToBuild, HostServices hostServices) - : this (projectFullPath, globalProperties, toolsVersion, targetsToBuild, hostServices, BuildRequestDataFlags.None) - { - } + public BuildRequestData (string projectFullPath, IDictionary<string, string> globalProperties, + string toolsVersion, string[] targetsToBuild, HostServices hostServices) + : this (projectFullPath, globalProperties, toolsVersion, targetsToBuild, hostServices, BuildRequestDataFlags.None) + { + } - public BuildRequestData (string projectFullPath, IDictionary<string, string> globalProperties, - string toolsVersion, string[] targetsToBuild, HostServices hostServices, BuildRequestDataFlags flags) - { - throw new NotImplementedException (); - } + public BuildRequestData (string projectFullPath, IDictionary<string, string> globalProperties, + string toolsVersion, string[] targetsToBuild, HostServices hostServices, BuildRequestDataFlags flags) + : this (new ProjectInstance (projectFullPath, globalProperties, toolsVersion), targetsToBuild, hostServices, flags) + { + ExplicitlySpecifiedToolsVersion = toolsVersion; + } - public string ExplicitlySpecifiedToolsVersion { get; private set; } - public BuildRequestDataFlags Flags { get; private set; } - public HostServices HostServices { get; private set; } - public string ProjectFullPath { get; private set; } - public ProjectInstance ProjectInstance { get; private set; } - public ICollection<string> TargetNames { get; private set; } + public string ExplicitlySpecifiedToolsVersion { get; private set; } - ICollection<ProjectPropertyInstance> GlobalProperties { - get { throw new NotImplementedException (); } - } - } + [MonoTODO ("unused")] + public BuildRequestDataFlags Flags { get; private set; } + + [MonoTODO ("unused")] + public HostServices HostServices { get; private set; } + + public string ProjectFullPath { + get { return ProjectInstance.FullPath; } + } + + [MonoTODO ("unused")] + public ProjectInstance ProjectInstance { get; private set; } + + [MonoTODO] + public IEnumerable<string> PropertiesToTransfer { get; private set; } + + [MonoTODO] + public ICollection<string> TargetNames { get; private set; } + + ICollection<ProjectPropertyInstance> GlobalProperties { + get { return ProjectInstance.Properties.Where (p => ProjectInstance.GlobalProperties.Any (i => i.Key == p.Name)).ToArray (); } // we can use == as it should be identical match there. + } + } } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/BuildResult.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/BuildResult.cs index 7286a247d2..479b137a4f 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/BuildResult.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/BuildResult.cs @@ -27,67 +27,75 @@ using System; using System.Collections.Generic; +using System.Linq; namespace Microsoft.Build.Execution { - public class BuildResult - { - public void AddResultsForTarget (string target, TargetResult result) - { - throw new NotImplementedException (); - } - - public bool HasResultsForTarget (string target) - { - throw new NotImplementedException (); - } - - public void MergeResults (BuildResult results) - { - throw new NotImplementedException (); - } - - public bool CircularDependency { - get { throw new NotImplementedException (); } - } - - public int ConfigurationId { - get { throw new NotImplementedException (); } - } - - public Exception Exception { - get { throw new NotImplementedException (); } - set { throw new NotImplementedException (); } - } - - public int GlobalRequestId { - get { throw new NotImplementedException (); } - } - - public ITargetResult this [string target] { - get { throw new NotImplementedException (); } - } - - public int NodeRequestId { - get { throw new NotImplementedException (); } - } - - public BuildResultCode OverallResult { - get { throw new NotImplementedException (); } - } - - public int ParentGlobalRequestId { - get { throw new NotImplementedException (); } - } - - public IDictionary<string, TargetResult> ResultsByTarget { - get { throw new NotImplementedException (); } - } - - public int SubmissionId { - get { throw new NotImplementedException (); } - } - - } + public class BuildResult + { + public BuildResult () + { + ResultsByTarget = new Dictionary<string, TargetResult> (); + } + + public void AddResultsForTarget (string target, TargetResult result) + { + ResultsByTarget.Add (target, result); + } + + public bool HasResultsForTarget (string target) + { + return ResultsByTarget.ContainsKey (target); + } + + public void MergeResults (BuildResult results) + { + if (ConfigurationId != results.ConfigurationId) + throw new InvalidOperationException ("Argument BuildResults have inconsistent ConfigurationId."); + if (GlobalRequestId != results.GlobalRequestId) + throw new InvalidOperationException ("Argument BuildResults have inconsistent GlobalRequestId."); + if (NodeRequestId != results.NodeRequestId) + throw new InvalidOperationException ("Argument BuildResults have inconsistent NodeRequestId."); + if (ParentGlobalRequestId != results.ParentGlobalRequestId) + throw new InvalidOperationException ("Argument BuildResults have inconsistent ParentGlobalRequestId."); + if (SubmissionId != results.SubmissionId) + throw new InvalidOperationException ("Argument BuildResults have inconsistent SubmissionId."); + + CircularDependency |= results.CircularDependency; + Exception = Exception ?? results.Exception; + foreach (var p in results.ResultsByTarget) + ResultsByTarget.Add (p.Key, p.Value); + } + + public bool CircularDependency { get; internal set; } + + public int ConfigurationId { get; internal set; } + + public Exception Exception { get; set; } + + public int GlobalRequestId { get; internal set; } + + public ITargetResult this [string target] { + get { return ResultsByTarget [target]; } + } + + public int NodeRequestId { get; internal set; } + + BuildResultCode? overall_result; + public BuildResultCode OverallResult { + get { + if (overall_result == null) + throw new InvalidOperationException ("Build has not finished"); + return overall_result.Value; + } + internal set { overall_result = value; } + } + + public int ParentGlobalRequestId { get; internal set; } + + public IDictionary<string, TargetResult> ResultsByTarget { get; private set; } + + public int SubmissionId { get; internal set; } + } } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/BuildSubmission.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/BuildSubmission.cs index 3a8a612b93..91a9823ce8 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/BuildSubmission.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/BuildSubmission.cs @@ -2,8 +2,9 @@ // // Author: // Rolf Bjarne Kvinge (rolf@xamarin.com) +// Atsushi Enomoto (atsushi@xamarin.com) // -// Copyright (C) 2011 Xamarin Inc. +// Copyright (C) 2011,2013 Xamarin Inc. // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the @@ -26,15 +27,94 @@ // using System; +using System.Threading; +using System.Threading.Tasks; +using System.Linq; +using Microsoft.Build.Framework; +using Microsoft.Build.Internal; +using System.Collections.Generic; namespace Microsoft.Build.Execution { - public class BuildSubmission - { - private BuildSubmission () - { - throw new NotImplementedException (); - } - } + public class BuildSubmission + { + static Random rnd = new Random (); + + internal BuildSubmission (BuildManager build, BuildRequestData requestData) + { + BuildManager = build; + this.request = requestData; + SubmissionId = rnd.Next (); + } + + BuildRequestData request; + BuildSubmissionCompleteCallback callback; + bool is_started, is_completed, is_canceled; + ManualResetEvent wait_handle = new ManualResetEvent (true); + + public object AsyncContext { get; private set; } + public BuildManager BuildManager { get; private set; } + public BuildResult BuildResult { get; set; } + public bool IsCompleted { + get { return is_completed; } + } + public int SubmissionId { get; private set; } + public WaitHandle WaitHandle { + get { return wait_handle; } + } + + internal BuildRequestData BuildRequest { + get { return this.request; } + } + + internal void Cancel () + { + if (is_canceled) + throw new InvalidOperationException ("Build has already canceled"); + is_canceled = true; + } + + public BuildResult Execute () + { + ExecuteAsync (null, null); + WaitHandle.WaitOne (); + return BuildResult; + } + + internal BuildResult InternalExecute () + { + BuildResult = new BuildResult () { SubmissionId = SubmissionId }; + try { + var engine = new BuildEngine4 (this); + string toolsVersion = request.ExplicitlySpecifiedToolsVersion ?? request.ProjectInstance.ToolsVersion ?? BuildManager.OngoingBuildParameters.DefaultToolsVersion; + var outputs = new Dictionary<string,string> (); + engine.BuildProject (() => is_canceled, BuildResult, request.ProjectInstance, request.TargetNames, BuildManager.OngoingBuildParameters.GlobalProperties, outputs, toolsVersion); + } catch (Exception ex) { + BuildResult.Exception = ex; + BuildResult.OverallResult = BuildResultCode.Failure; + } + is_completed = true; + if (callback != null) + callback (this); + wait_handle.Set (); + return BuildResult; + } + + public void ExecuteAsync (BuildSubmissionCompleteCallback callback, object context) + { + if (is_completed) + throw new InvalidOperationException ("Build has already completed"); + if (is_canceled) + throw new InvalidOperationException ("Build has already canceled"); + if (is_started) + throw new InvalidOperationException ("Build has already started"); + is_started = true; + this.AsyncContext = context; + this.callback = callback; + wait_handle.Reset (); + + BuildManager.BuildNodeManager.Enqueue (this); + } + } } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/BuildSubmissionCompleteCallback.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/BuildSubmissionCompleteCallback.cs new file mode 100644 index 0000000000..fba101a2fd --- /dev/null +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/BuildSubmissionCompleteCallback.cs @@ -0,0 +1,5 @@ +namespace Microsoft.Build.Execution +{ + public delegate void BuildSubmissionCompleteCallback (BuildSubmission submission); +} + diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/HostServices.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/HostServices.cs index 2389bdf330..e68755affe 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/HostServices.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/HostServices.cs @@ -26,42 +26,95 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -using Microsoft.Build.Framework; using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Build.Framework; namespace Microsoft.Build.Execution { - public class HostServices - { - public ITaskHost GetHostObject (string projectFile, string targetName, string taskName) - { - throw new NotImplementedException (); - } + public class HostServices + { + class HostObjectRegistration + { + public string ProjectFile { get; set; } + public string TargetName { get; set; } + public string TaskName { get; set; } + public ITaskHost HostObject { get; set; } + } + + readonly List<HostObjectRegistration> hosts = new List<HostObjectRegistration> (); + readonly Dictionary<string,NodeAffinity> node_affinities = new Dictionary<string, NodeAffinity> (); + + HostObjectRegistration GetHostRegistration (string projectFile, string targetName, string taskName) + { + if (projectFile == null) + throw new ArgumentNullException ("projectFile"); + if (targetName == null) + throw new ArgumentNullException ("targetName"); + if (taskName == null) + throw new ArgumentNullException ("taskName"); + return hosts.FirstOrDefault (h => + string.Equals (projectFile, h.ProjectFile, StringComparison.OrdinalIgnoreCase) && + string.Equals (targetName, h.TargetName, StringComparison.OrdinalIgnoreCase) && + string.Equals (taskName, h.TaskName, StringComparison.OrdinalIgnoreCase)); + } + + public ITaskHost GetHostObject (string projectFile, string targetName, string taskName) + { + var reg = GetHostRegistration (projectFile, targetName, taskName); + return reg != null ? reg.HostObject : null; + } - public NodeAffinity GetNodeAffinity (string projectFile) - { - throw new NotImplementedException (); - } + public NodeAffinity GetNodeAffinity (string projectFile) + { + if (projectFile == null) + throw new ArgumentNullException ("projectFile"); + NodeAffinity na; + return node_affinities.TryGetValue (projectFile, out na) ? na : NodeAffinity.Any; + } + + IEnumerable<HostObjectRegistration> GetRegistrationsByProject (string project) + { + return hosts.Where (h => string.Equals (project, h.ProjectFile, StringComparison.OrdinalIgnoreCase)); + } - public void OnRenameProject (string oldFullPath, string newFullPath) - { - throw new NotImplementedException (); - } + public void OnRenameProject (string oldFullPath, string newFullPath) + { + if (oldFullPath == null) + throw new ArgumentNullException ("oldFullPath"); + if (newFullPath == null) + throw new ArgumentNullException ("newFullPath"); + foreach (var reg in GetRegistrationsByProject (oldFullPath)) + reg.ProjectFile = newFullPath; + } - public void RegisterHostObject (string projectFile, string targetName, string taskName, ITaskHost hostObject) - { - throw new NotImplementedException (); - } + public void RegisterHostObject (string projectFile, string targetName, string taskName, ITaskHost hostObject) + { + if (hostObject == null) + throw new ArgumentNullException ("hostObject"); + var reg = GetHostRegistration (projectFile, targetName, taskName); + if (reg != null) + reg.HostObject = hostObject; + else + hosts.Add (new HostObjectRegistration () { ProjectFile = projectFile, TargetName = targetName, TaskName = taskName, HostObject = hostObject }); + } - public void SetNodeAffinity (string projectFile, NodeAffinity nodeAffinity) - { - throw new NotImplementedException (); - } + public void SetNodeAffinity (string projectFile, NodeAffinity nodeAffinity) + { + if (projectFile == null) + throw new ArgumentNullException ("projectFile"); + node_affinities [projectFile] = nodeAffinity; + } - public void UnregisterProject (string projectFullPath) - { - throw new NotImplementedException (); - } - } + public void UnregisterProject (string projectFullPath) + { + if (projectFullPath == null) + throw new ArgumentNullException ("projectFullPath"); + var removed = GetRegistrationsByProject (projectFullPath).ToArray (); + foreach (var r in removed) + hosts.Remove (r); + } + } } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Logging/WriteHandler.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/NodeEngineShutdownReason.cs index 29ce75c414..b88423f2a8 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Logging/WriteHandler.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/NodeEngineShutdownReason.cs @@ -1,9 +1,10 @@ -// WriteHandler.cs +// +// NodeEngineShutdownReason.cs // // Author: -// Rolf Bjarne Kvinge (rolf@xamarin.com) +// Atsushi Enomoto (atsushi@xamarin.com) // -// Copyright (C) 2011 Xamarin Inc. +// Copyright (C) 2013 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 @@ -15,7 +16,7 @@ // // 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 @@ -24,9 +25,23 @@ // 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 Microsoft.Build.BuildEngine; +using Microsoft.Build.Execution; +using Microsoft.Build.Framework; +using Microsoft.Build.Evaluation; +using System.Linq; +using System.IO; -namespace Microsoft.Build.Logging +namespace Microsoft.Build.Internal { - public delegate void WriteHandler (string message); + public enum NodeEngineShutdownReason + { + BuildComplete, + BuildCompleteReuse, + ConnectionFailed, + Error, + } } - diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/OutOfProcNode.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/OutOfProcNode.cs new file mode 100644 index 0000000000..7237052d19 --- /dev/null +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/OutOfProcNode.cs @@ -0,0 +1,48 @@ +// +// OutOfProcNode.cs +// +// Author: +// Atsushi Enomoto (atsushi@xamarin.com) +// +// Copyright (C) 2013 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 Microsoft.Build.BuildEngine; +using Microsoft.Build.Execution; +using Microsoft.Build.Framework; +using Microsoft.Build.Evaluation; +using System.Linq; +using System.IO; + +namespace Microsoft.Build.Internal +{ + // from MSDN: this class has deprecated and there is no alternative. + public class OutOfProcNode + { + public NodeEngineShutdownReason Run (out Exception shutdownException) + { + throw new NotImplementedException (); + } + } +} diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectInstance.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectInstance.cs index 4661496222..91ce68a040 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectInstance.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectInstance.cs @@ -3,8 +3,9 @@ // // Author: // Rolf Bjarne Kvinge (rolf@xamarin.com) +// Atsushi Enomoto (atsushi@xamarin.com) // -// Copyright (C) 2011 Xamarin Inc. +// Copyright (C) 2011,2013 Xamarin Inc. // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the @@ -27,13 +28,34 @@ // using System; +using System.Collections; using System.Collections.Generic; +using System.Linq; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; using Microsoft.Build.Framework; +using Microsoft.Build.Internal.Expressions; using Microsoft.Build.Logging; +// +// It is not always consistent to reuse Project and its evaluation stuff mostly because +// both BuildParameters.ctor() and Project.ctor() takes arbitrary ProjectCollection, which are not very likely eqivalent +// (as BuildParameters.ctor(), unlike Project.ctor(...), is known to create a new ProjectCollection instance). +// +// However, that inconsistency could happen even if you only use ProjectInstance and BuildParameters. +// They both have constructors that take ProjectCollection and there is no guarantee that the arguments are the same. +// BuildManager.Build() does not fail because of inconsistent ProjectCollection instance on .NET. +// +// Anyhow, I'm not going to instantiate Project within ProjectInstance code for another reason: +// ProjectCollection.GetLoadedProject() does not return any Project instnace for corresponding ProjectInstance +// (or I should say, ProjectRootElement for both). +using Microsoft.Build.Internal; +using System.Xml; +using Microsoft.Build.Exceptions; +using System.IO; + + namespace Microsoft.Build.Execution { public class ProjectInstance @@ -59,35 +81,210 @@ namespace Microsoft.Build.Execution public ProjectInstance (ProjectRootElement xml, IDictionary<string, string> globalProperties, string toolsVersion, ProjectCollection projectCollection) { - InitializeProperties (); - - throw new NotImplementedException (); + projects = projectCollection; + global_properties = globalProperties ?? new Dictionary<string, string> (); + tools_version = !string.IsNullOrEmpty (toolsVersion) ? toolsVersion : + !string.IsNullOrEmpty (xml.ToolsVersion) ? xml.ToolsVersion : + projects.DefaultToolsVersion; + InitializeProperties (xml, null); } public ProjectInstance (string projectFile, IDictionary<string, string> globalProperties, string toolsVersion, ProjectCollection projectCollection) + : this (ProjectRootElement.Create (projectFile), globalProperties, toolsVersion, projectCollection) + { + } + + ProjectCollection projects; + IDictionary<string, string> global_properties; + + string full_path, directory; + #if NET_4_5 + ElementLocation location; + #endif + + Dictionary<string, ProjectItemDefinitionInstance> item_definitions; + List<ResolvedImport> raw_imports; // maybe we don't need this... + List<ProjectItemInstance> all_evaluated_items; + List<ProjectItemInstance> raw_items; + List<ProjectPropertyInstance> properties; + Dictionary<string, ProjectTargetInstance> targets; + string tools_version; + + List<string> GetDefaultTargets (ProjectRootElement xml) { - InitializeProperties (); + var ret = xml.DefaultTargets.Split (item_target_sep, StringSplitOptions.RemoveEmptyEntries).Select (s => s.Trim ()).ToList (); + if (ret.Count == 0 && xml.Targets.Any ()) + ret.Add (xml.Targets.First ().Name); + return ret; + } + + void InitializeProperties (ProjectRootElement xml, ProjectInstance parent) + { + #if NET_4_5 + location = xml.Location; + #endif + full_path = xml.FullPath; + directory = string.IsNullOrWhiteSpace (xml.DirectoryPath) ? System.IO.Directory.GetCurrentDirectory () : xml.DirectoryPath; + DefaultTargets = GetDefaultTargets (xml); + InitialTargets = xml.InitialTargets.Split (item_target_sep, StringSplitOptions.RemoveEmptyEntries).Select (s => s.Trim ()).ToList (); + + raw_imports = new List<ResolvedImport> (); + item_definitions = new Dictionary<string, ProjectItemDefinitionInstance> (); + targets = new Dictionary<string, ProjectTargetInstance> (); + raw_items = new List<ProjectItemInstance> (); - throw new NotImplementedException (); + // FIXME: this is likely hack. Test ImportedProject.Properties to see what exactly should happen. + if (parent != null) { + properties = parent.properties; + } else { + properties = new List<ProjectPropertyInstance> (); + + foreach (DictionaryEntry p in Environment.GetEnvironmentVariables ()) + // FIXME: this is kind of workaround for unavoidable issue that PLATFORM=* is actually given + // on some platforms and that prevents setting default "PLATFORM=AnyCPU" property. + if (!string.Equals ("PLATFORM", (string) p.Key, StringComparison.OrdinalIgnoreCase)) + this.properties.Add (new ProjectPropertyInstance ((string) p.Key, false, (string) p.Value)); + foreach (var p in global_properties) + this.properties.Add (new ProjectPropertyInstance (p.Key, false, p.Value)); + var tools = projects.GetToolset (tools_version) ?? projects.GetToolset (projects.DefaultToolsVersion); + foreach (var p in projects.GetReservedProperties (tools, this, xml)) + this.properties.Add (p); + foreach (var p in ProjectCollection.GetWellKnownProperties (this)) + this.properties.Add (p); + } + + ProcessXml (parent, xml); } - void InitializeProperties () + static readonly char [] item_target_sep = {';'}; + + void ProcessXml (ProjectInstance parent, ProjectRootElement xml) { - DefaultTargets = new List<string> (); - InitialTargets = new List<string> (); + TaskDatabase = new BuildTaskDatabase (this, xml); + + // this needs to be initialized here (regardless of that items won't be evaluated at property evaluation; + // Conditions could incorrectly reference items and lack of this list causes NRE. + all_evaluated_items = new List<ProjectItemInstance> (); + + // property evaluation happens couple of times. + // At first step, all non-imported properties are evaluated TOO, WHILE those properties are being evaluated. + // This means, Include and IncludeGroup elements with Condition attribute MAY contain references to + // properties and they will be expanded. + var elements = EvaluatePropertiesAndImports (xml.Children).ToArray (); // ToArray(): to not lazily evaluate elements. + + // next, evaluate items + EvaluateItems (xml, elements); + + // finally, evaluate targets and tasks + EvaluateTasks (elements); } - Dictionary<string, string> global_properties = new Dictionary<string, string> (); + IEnumerable<ProjectElement> EvaluatePropertiesAndImports (IEnumerable<ProjectElement> elements) + { + // First step: evaluate Properties + foreach (var child in elements) { + yield return child; + var pge = child as ProjectPropertyGroupElement; + if (pge != null && EvaluateCondition (pge.Condition)) + foreach (var p in pge.Properties) + // do not allow overwriting reserved or well-known properties by user + if (!this.properties.Any (_ => (_.IsImmutable) && _.Name.Equals (p.Name, StringComparison.InvariantCultureIgnoreCase))) + if (EvaluateCondition (p.Condition)) + this.properties.Add (new ProjectPropertyInstance (p.Name, false, ExpandString (p.Value))); + + var ige = child as ProjectImportGroupElement; + if (ige != null && EvaluateCondition (ige.Condition)) { + foreach (var incc in ige.Imports) { + foreach (var e in Import (incc)) + yield return e; + } + } + var inc = child as ProjectImportElement; + if (inc != null && EvaluateCondition (inc.Condition)) + foreach (var e in Import (inc)) + yield return e; + } + } + internal IEnumerable<T> GetAllItems<T> (string include, string exclude, Func<string,T> creator, Func<string,ITaskItem> taskItemCreator, Func<string,bool> itemTypeCheck, Action<T,string> assignRecurse) + { + return ProjectCollection.GetAllItems<T> (ExpandString, include, exclude, creator, taskItemCreator, Directory, assignRecurse, + t => all_evaluated_items.Any (i => i.EvaluatedInclude == t.ItemSpec && itemTypeCheck (i.ItemType))); + } + + void EvaluateItems (ProjectRootElement xml, IEnumerable<ProjectElement> elements) + { + foreach (var child in elements) { + var ige = child as ProjectItemGroupElement; + if (ige != null) { + foreach (var p in ige.Items) { + if (!EvaluateCondition (ige.Condition) || !EvaluateCondition (p.Condition)) + continue; + Func<string,ProjectItemInstance> creator = s => new ProjectItemInstance (this, p.ItemType, p.Metadata.Select (m => new KeyValuePair<string,string> (m.Name, m.Value)).ToList (), s); + foreach (var item in GetAllItems (p.Include, p.Exclude, creator, s => new ProjectTaskItem (p, s), it => string.Equals (it, p.ItemType, StringComparison.OrdinalIgnoreCase), (t, s) => t.RecursiveDir = s)) { + raw_items.Add (item); + all_evaluated_items.Add (item); + } + } + } + var def = child as ProjectItemDefinitionGroupElement; + if (def != null) { + foreach (var p in def.ItemDefinitions) { + if (EvaluateCondition (p.Condition)) { + ProjectItemDefinitionInstance existing; + if (!item_definitions.TryGetValue (p.ItemType, out existing)) + item_definitions.Add (p.ItemType, (existing = new ProjectItemDefinitionInstance (p))); + existing.AddItems (p); + } + } + } + } + all_evaluated_items.Sort ((p1, p2) => string.Compare (p1.ItemType, p2.ItemType, StringComparison.OrdinalIgnoreCase)); + } + + void EvaluateTasks (IEnumerable<ProjectElement> elements) + { + foreach (var child in elements) { + var te = child as ProjectTargetElement; + if (te != null) + this.targets.Add (te.Name, new ProjectTargetInstance (te)); + } + } + + IEnumerable<ProjectElement> Import (ProjectImportElement import) + { + string dir = projects.GetEvaluationTimeThisFileDirectory (() => FullPath); + string path = WindowsCompatibilityExtensions.NormalizeFilePath (ExpandString (import.Project)); + path = Path.IsPathRooted (path) ? path : dir != null ? Path.Combine (dir, path) : Path.GetFullPath (path); + if (projects.OngoingImports.Contains (path)) + throw new InvalidProjectFileException (import.Location, null, string.Format ("Circular imports was detected: {0} is already on \"importing\" stack", path)); + projects.OngoingImports.Push (path); + try { + using (var reader = XmlReader.Create (path)) { + var root = ProjectRootElement.Create (reader, projects); + if (DefaultTargets.Count == 0) + DefaultTargets.AddRange (GetDefaultTargets (root)); + raw_imports.Add (new ResolvedImport (import, root, true)); + return this.EvaluatePropertiesAndImports (root.Children).ToArray (); + } + } finally { + projects.OngoingImports.Pop (); + } + } + + internal IEnumerable<ProjectItemInstance> AllEvaluatedItems { + get { return all_evaluated_items; } + } + public List<string> DefaultTargets { get; private set; } public string Directory { - get { throw new NotImplementedException (); } + get { return directory; } } public string FullPath { - get { throw new NotImplementedException (); } + get { return full_path; } } public IDictionary<string, string> GlobalProperties { @@ -103,33 +300,33 @@ namespace Microsoft.Build.Execution #endif public IDictionary<string, ProjectItemDefinitionInstance> ItemDefinitions { - get { throw new NotImplementedException (); } + get { return item_definitions; } } public ICollection<ProjectItemInstance> Items { - get { throw new NotImplementedException (); } + get { return all_evaluated_items; } } public ICollection<string> ItemTypes { - get { throw new NotImplementedException (); } + get { return all_evaluated_items.Select (i => i.ItemType).Distinct ().ToArray (); } } #if NET_4_5 public ElementLocation ProjectFileLocation { - get { throw new NotImplementedException (); } + get { return location; } } #endif public ICollection<ProjectPropertyInstance> Properties { - get { throw new NotImplementedException (); } + get { return properties; } } public IDictionary<string, ProjectTargetInstance> Targets { - get { throw new NotImplementedException (); } + get { return targets; } } public string ToolsVersion { - get { throw new NotImplementedException (); } + get { return tools_version; } } public ProjectItemInstance AddItem (string itemType, string evaluatedInclude) @@ -139,7 +336,10 @@ namespace Microsoft.Build.Execution public ProjectItemInstance AddItem (string itemType, string evaluatedInclude, IEnumerable<KeyValuePair<string, string>> metadata) { - throw new NotImplementedException (); + var item = new ProjectItemInstance (this, itemType, metadata, evaluatedInclude); + raw_items.Add (item); + all_evaluated_items.Add (item); + return item; } public bool Build () @@ -154,7 +354,7 @@ namespace Microsoft.Build.Execution public bool Build (IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers) { - return Build ((string []) null, loggers, remoteLoggers); + return Build (DefaultTargets.ToArray (), loggers, remoteLoggers); } public bool Build (string target, IEnumerable<ILogger> loggers) @@ -180,12 +380,20 @@ namespace Microsoft.Build.Execution public bool Build (string[] targets, IEnumerable<ILogger> loggers, out IDictionary<string, TargetResult> targetOutputs) { - return Build (targets, loggers, new ForwardingLoggerRecord [0], out targetOutputs); + return Build (targets, loggers, new ForwardingLoggerRecord [0], out targetOutputs); } public bool Build (string[] targets, IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers, out IDictionary<string, TargetResult> targetOutputs) { - throw new NotImplementedException (); + var manager = new BuildManager (); + var parameters = new BuildParameters (projects) { + ForwardingLoggers = remoteLoggers, + Loggers = loggers, + }; + var requestData = new BuildRequestData (this, targets); + var result = manager.Build (parameters, requestData); + targetOutputs = result.ResultsByTarget; + return result.OverallResult == BuildResultCode.Success; } public ProjectInstance DeepCopy () @@ -200,17 +408,22 @@ namespace Microsoft.Build.Execution public bool EvaluateCondition (string condition) { - throw new NotImplementedException (); + return string.IsNullOrWhiteSpace (condition) || new ExpressionEvaluator (this, null).EvaluateAsBoolean (condition); } public string ExpandString (string unexpandedValue) { - throw new NotImplementedException (); + return ExpandString (unexpandedValue, null); + } + + string ExpandString (string unexpandedValue, string replacementForMissingStuff) + { + return new ExpressionEvaluator (this, replacementForMissingStuff).Evaluate (unexpandedValue); } public ICollection<ProjectItemInstance> GetItems (string itemType) { - throw new NotImplementedException (); + return new CollectionFromEnumerable<ProjectItemInstance> (Items.Where (p => p.ItemType.Equals (itemType, StringComparison.OrdinalIgnoreCase))); } public IEnumerable<ProjectItemInstance> GetItemsByItemTypeAndEvaluatedInclude (string itemType, string evaluatedInclude) @@ -220,27 +433,36 @@ namespace Microsoft.Build.Execution public ProjectPropertyInstance GetProperty (string name) { - throw new NotImplementedException (); + return properties.FirstOrDefault (p => p.Name.Equals (name, StringComparison.OrdinalIgnoreCase)); } public string GetPropertyValue (string name) { - throw new NotImplementedException (); + var prop = GetProperty (name); + return prop != null ? prop.EvaluatedValue : string.Empty; } public bool RemoveItem (ProjectItemInstance item) { - throw new NotImplementedException (); + // yeah, this raw_items should vanish... + raw_items.Remove (item); + return all_evaluated_items.Remove (item); } public bool RemoveProperty (string name) { - throw new NotImplementedException (); + var removed = properties.FirstOrDefault (p => p.Name.Equals (name, StringComparison.OrdinalIgnoreCase)); + if (removed == null) + return false; + properties.Remove (removed); + return true; } public ProjectPropertyInstance SetProperty (string name, string evaluatedValue) { - throw new NotImplementedException (); + var p = new ProjectPropertyInstance (name, false, evaluatedValue); + properties.Add (p); + return p; } public ProjectRootElement ToProjectRootElement () @@ -259,32 +481,46 @@ namespace Microsoft.Build.Execution public static string GetEvaluatedItemIncludeEscaped (ProjectItemDefinitionInstance item) { + // ?? ItemDefinition does not have Include attribute. What's the point here? throw new NotImplementedException (); } public static string GetEvaluatedItemIncludeEscaped (ProjectItemInstance item) { - throw new NotImplementedException (); + return ProjectCollection.Escape (item.EvaluatedInclude); } public static string GetMetadataValueEscaped (ProjectMetadataInstance metadatum) { - throw new NotImplementedException (); + return ProjectCollection.Escape (metadatum.EvaluatedValue); } public static string GetMetadataValueEscaped (ProjectItemDefinitionInstance item, string name) { - throw new NotImplementedException (); + var md = item.Metadata.FirstOrDefault (m => m.Name.Equals (name, StringComparison.OrdinalIgnoreCase)); + return md != null ? ProjectCollection.Escape (md.EvaluatedValue) : null; } public static string GetMetadataValueEscaped (ProjectItemInstance item, string name) { - throw new NotImplementedException (); + var md = item.Metadata.FirstOrDefault (m => m.Name.Equals (name, StringComparison.OrdinalIgnoreCase)); + return md != null ? ProjectCollection.Escape (md.EvaluatedValue) : null; } public static string GetPropertyValueEscaped (ProjectPropertyInstance property) { - throw new NotImplementedException (); + // WTF happens here. + //return ProjectCollection.Escape (property.EvaluatedValue); + return property.EvaluatedValue; + } + + internal BuildTaskDatabase TaskDatabase { get; private set; } + + internal string GetFullPath (string pathRelativeToProject) + { + if (Path.IsPathRooted (pathRelativeToProject)) + return pathRelativeToProject; + return Path.GetFullPath (Path.Combine (Directory, pathRelativeToProject)); } } } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectItemDefinitionInstance.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectItemDefinitionInstance.cs index 4641a951ce..fb10ceec17 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectItemDefinitionInstance.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectItemDefinitionInstance.cs @@ -4,7 +4,7 @@ // Author: // Atsushi Enomoto (atsushi@veritas-vos-liberabit.com) // -// Copyright (C) 2012 Xamarin Inc. +// Copyright (C) 2012,2013 Xamarin Inc. // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the @@ -29,29 +29,43 @@ using Microsoft.Build.Framework; using System; using System.Collections.Generic; +using Microsoft.Build.Construction; +using System.Linq; namespace Microsoft.Build.Execution { public class ProjectItemDefinitionInstance { - internal ProjectItemDefinitionInstance () + internal ProjectItemDefinitionInstance (ProjectItemDefinitionElement xml) { + ItemType = xml.ItemType; + AddItems (xml); } - public string ItemType { - get { throw new NotImplementedException (); } - } + List<ProjectMetadataInstance> metadata = new List<ProjectMetadataInstance> (); + + public string ItemType { get; private set; } public ICollection<ProjectMetadataInstance> Metadata { - get { throw new NotImplementedException (); } + get { return metadata; } } public int MetadataCount { - get { throw new NotImplementedException (); } + get { return metadata.Count; } } public IEnumerable<string> MetadataNames { - get { throw new NotImplementedException (); } + get { return metadata.Select (m => m.Name).ToArray (); } + } + + internal void AddItems (ProjectItemDefinitionElement xml) + { + foreach (var item in xml.Metadata) { + var existing = metadata.FirstOrDefault (i => i.Name == item.Name); + if (existing != null) + metadata.Remove (existing); + metadata.Add (new ProjectMetadataInstance (item.Name, item.Value)); + } } } } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectItemGroupTaskInstance.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectItemGroupTaskInstance.cs new file mode 100644 index 0000000000..bf9d03f05f --- /dev/null +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectItemGroupTaskInstance.cs @@ -0,0 +1,82 @@ +// +// ProjectItemGroupTaskInstance.cs +// +// Author: +// Atsushi Enomoto (atsushi@xamarin.com) +// +// (C) 2013 Xamarin Inc. +// +// 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.Generic; +using System.Linq; +using Microsoft.Build.Construction; + +namespace Microsoft.Build.Execution +{ + public sealed class ProjectItemGroupTaskInstance : ProjectTargetInstanceChild + { + internal ProjectItemGroupTaskInstance (ProjectItemGroupElement xml) + { + condition = xml.Condition; + condition_location = xml.ConditionLocation; + //this.FullPath = fullPath; + location = xml.Location; + + Items = xml.Items.Select (item => new ProjectItemGroupTaskItemInstance (item)).ToArray (); + } + + readonly string condition; + readonly ElementLocation condition_location, location; + + public override string Condition { + get { return condition; } + } + + #if NET_4_5 + public + #else + internal + #endif + override ElementLocation ConditionLocation { + get { return condition_location; } + } + + #if NET_4_5 + public + #else + internal + #endif + override ElementLocation Location { + get { return location; } + } + + #if NET_4_5 + public + #else + internal + #endif + ElementLocation ExecuteTargetsLocation { get; private set; } + + public ICollection<ProjectItemGroupTaskItemInstance> Items { get; private set; } + } +} + diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectItemGroupTaskItemInstance.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectItemGroupTaskItemInstance.cs new file mode 100644 index 0000000000..8d947b72ba --- /dev/null +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectItemGroupTaskItemInstance.cs @@ -0,0 +1,95 @@ +// +// ProjectItemGroupTaskItemInstance.cs +// +// Author: +// Atsushi Enomoto (atsushi@xamarin.com) +// +// (C) 2013 Xamarin Inc. +// +// 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 Microsoft.Build.Construction; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Build.Execution +{ + public class ProjectItemGroupTaskItemInstance + { + internal ProjectItemGroupTaskItemInstance (ProjectItemElement xml) + { + Condition = xml.Condition; + Exclude = xml.Exclude; + Include = xml.Include; + ItemType = xml.ItemType; + Metadata = xml.Metadata.Select (m => new ProjectItemGroupTaskMetadataInstance (m)).ToArray (); + Remove = xml.Remove; + #if NET_4_5 + KeepDuplicates = xml.KeepDuplicates; + KeepMetadata = xml.KeepMetadata; + RemoveMetadata = xml.RemoveMetadata; + + ConditionLocation = xml.ConditionLocation; + ExcludeLocation = xml.ExcludeLocation; + IncludeLocation = xml.IncludeLocation; + Location = xml.Location; + KeepDuplicatesLocation = xml.KeepDuplicatesLocation; + RemoveLocation = xml.RemoveLocation; + RemoveMetadataLocation = xml.RemoveMetadataLocation; + #endif + } + + public string Condition { get; private set; } + + public string Exclude { get; private set; } + + public string Include { get; private set; } + + public string ItemType { get; private set; } + + public string KeepDuplicates { get; private set; } + + public string KeepMetadata { get; private set; } + + public ICollection<ProjectItemGroupTaskMetadataInstance> Metadata { get; private set; } + + public string Remove { get; private set; } + + public string RemoveMetadata { get; private set; } + #if NET_4_5 + public ElementLocation ConditionLocation { get; private set; } + + public ElementLocation ExcludeLocation { get; private set; } + + public ElementLocation IncludeLocation { get; private set; } + + public ElementLocation KeepDuplicatesLocation { get; private set; } + + public ElementLocation KeepMetadataLocation { get; private set; } + + public ElementLocation Location { get; private set; } + + public ElementLocation RemoveLocation { get; private set; } + + public ElementLocation RemoveMetadataLocation { get; private set; } + #endif + } +} + diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectItemGroupTaskMetadataInstance.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectItemGroupTaskMetadataInstance.cs new file mode 100644 index 0000000000..ad871731c5 --- /dev/null +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectItemGroupTaskMetadataInstance.cs @@ -0,0 +1,58 @@ +// +// ProjectItemGroupTaskMetadataInstance.cs +// +// Author: +// Atsushi Enomoto (atsushi@xamarin.com) +// +// (C) 2013 Xamarin Inc. +// +// 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.Generic; +using Microsoft.Build.Construction; + +namespace Microsoft.Build.Execution +{ + public sealed class ProjectItemGroupTaskMetadataInstance + { + internal ProjectItemGroupTaskMetadataInstance (ProjectMetadataElement xml) + { + Condition = xml.Condition; + Name = xml.Name; + Value = xml.Value; + #if NET_4_5 + ConditionLocation = xml.ConditionLocation; + Location = xml.Location; + #endif + } + public string Condition { get; private set; } + + public string Name { get; private set; } + + public string Value { get; private set; } + #if NET_4_5 + public ElementLocation ConditionLocation { get; private set; } + + public ElementLocation Location { get; private set; } + #endif + } +} + diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectItemInstance.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectItemInstance.cs index 0e27811ef8..720c4b32b1 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectItemInstance.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectItemInstance.cs @@ -3,8 +3,9 @@ // // Author: // Rolf Bjarne Kvinge (rolf@xamarin.com) +// Atsushi Enomoto (atsushi@xamarin.com) // -// Copyright (C) 2011 Xamarin Inc. +// Copyright (C) 2011,2013 Xamarin Inc. // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the @@ -26,152 +27,191 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -using Microsoft.Build.Framework; using System; using System.Collections.Generic; +using System.Linq; +using Microsoft.Build.Framework; +using Microsoft.Build.Evaluation; +using System.Collections; +using Microsoft.Build.Construction; +using System.Globalization; +using System.IO; namespace Microsoft.Build.Execution { - public class ProjectItemInstance + public class ProjectItemInstance : ITaskItem2 - { - private ProjectItemInstance () - { - throw new NotImplementedException (); - } - - public ProjectMetadataInstance GetMetadata (string name) - { - throw new NotImplementedException (); - } - - public string GetMetadataValue (string name) - { - throw new NotImplementedException (); - } - - public bool HasMetadata (string name) - { - throw new NotImplementedException (); - } - - public void RemoveMetadata (string metadataName) - { - throw new NotImplementedException (); - } - - public void SetMetadata (IEnumerable<KeyValuePair<string, string>> metadataDictionary) - { - throw new NotImplementedException (); - } - - public ProjectMetadataInstance SetMetadata (string name, string evaluatedValue) - { - throw new NotImplementedException (); - } - - public int DirectMetadataCount { - get { throw new NotImplementedException (); } - } - - public string EvaluatedInclude { - get { throw new NotImplementedException (); } - set { throw new NotImplementedException (); } - } - - public string ItemType { - get { throw new NotImplementedException (); } - } - - public IEnumerable<ProjectMetadataInstance> Metadata { - get { throw new NotImplementedException (); } - } - - public int MetadataCount { - get { throw new NotImplementedException (); } - } - - public ICollection<string> MetadataNames { - get { throw new NotImplementedException (); } - } - - public ProjectInstance Project { - get { throw new NotImplementedException (); } - } - - #region ITaskItem2 implementation - string ITaskItem2.GetMetadataValueEscaped (string metadataName) - { - throw new NotImplementedException (); - } - - void ITaskItem2.SetMetadataValueLiteral (string metadataName, string metadataValue) - { - throw new NotImplementedException (); - } - - System.Collections.IDictionary ITaskItem2.CloneCustomMetadataEscaped () - { - throw new NotImplementedException (); - } - - string ITaskItem2.EvaluatedIncludeEscaped { - get { - throw new NotImplementedException (); - } - set { - throw new NotImplementedException (); - } - } - #endregion - - #region ITaskItem implementation - System.Collections.IDictionary ITaskItem.CloneCustomMetadata () - { - throw new NotImplementedException (); - } - - void ITaskItem.CopyMetadataTo (ITaskItem destinationItem) - { - throw new NotImplementedException (); - } - - string ITaskItem.GetMetadata (string metadataName) - { - throw new NotImplementedException (); - } - - void ITaskItem.RemoveMetadata (string metadataName) - { - throw new NotImplementedException (); - } - - void ITaskItem.SetMetadata (string metadataName, string metadataValue) - { - throw new NotImplementedException (); - } - - string ITaskItem.ItemSpec { - get { - throw new NotImplementedException (); - } - set { - throw new NotImplementedException (); - } - } - - int ITaskItem.MetadataCount { - get { - throw new NotImplementedException (); - } - } - - System.Collections.ICollection ITaskItem.MetadataNames { - get { - throw new NotImplementedException (); - } - } - #endregion - } + { + internal ProjectItemInstance (ProjectInstance project, string itemType, IEnumerable<KeyValuePair<string,string>> metadata, string evaluatedInclude) + { + this.project = project; + this.evaluated_include = evaluatedInclude; + this.item_type = itemType; + this.metadata = new List<ProjectMetadataInstance> (); + SetMetadata (metadata); + } + + readonly ProjectInstance project; + readonly string item_type; + string evaluated_include; + readonly List<ProjectMetadataInstance> metadata; + + public ProjectMetadataInstance GetMetadata (string name) + { + if (name == null) + throw new ArgumentNullException ("name"); + // This does not return any Well Known metadata + return Metadata.FirstOrDefault (m => m.Name.Equals (name, StringComparison.OrdinalIgnoreCase)); + } + + public string GetMetadataValue (string name) + { + if (name == null) + throw new ArgumentNullException ("name"); + var wk = ProjectCollection.GetWellKnownMetadata (name, EvaluatedInclude, project.GetFullPath, RecursiveDir); + if (wk != null) + return wk; + var m = GetMetadata (name); + return m != null ? m.EvaluatedValue : null; + } + + public bool HasMetadata (string name) + { + return GetMetadata (name) != null; + } + + public void RemoveMetadata (string metadataName) + { + var m = GetMetadata (metadataName); + if (m != null) + metadata.Remove (m); + } + + public void SetMetadata (IEnumerable<KeyValuePair<string, string>> metadataDictionary) + { + foreach (var p in metadataDictionary) + SetMetadata (p.Key, p.Value); + } + + public ProjectMetadataInstance SetMetadata (string name, string evaluatedValue) + { + var m = metadata.FirstOrDefault (_ => _.Name.Equals (name, StringComparison.OrdinalIgnoreCase)); + if (m != null) + metadata.Remove (m); + m = new ProjectMetadataInstance (name, evaluatedValue); + metadata.Add (m); + return m; + } + + public int DirectMetadataCount { + get { throw new NotImplementedException (); } + } + + public string EvaluatedInclude { + get { return evaluated_include; } + set { + if (value == null) + throw new ArgumentNullException ("value"); + evaluated_include = value; + } + } + + public string ItemType { + get { return item_type; } + } + + public IEnumerable<ProjectMetadataInstance> Metadata { + get { return metadata; } + } + + public int MetadataCount { + get { return metadata.Count; } + } + + public ICollection<string> MetadataNames { + get { return metadata.Select (m => m.Name).ToArray (); } + } + + public ProjectInstance Project { + get { return project; } + } + + internal string RecursiveDir { get; set; } + + #region ITaskItem2 implementation + + string ITaskItem2.GetMetadataValueEscaped (string metadataName) + { + return ProjectCollection.Escape (GetMetadataValue (metadataName)); + } + + void ITaskItem2.SetMetadataValueLiteral (string metadataName, string metadataValue) + { + SetMetadata (metadataName, metadataValue); + } + + System.Collections.IDictionary ITaskItem2.CloneCustomMetadataEscaped () + { + var dic = ((ITaskItem) this).CloneCustomMetadata (); + foreach (DictionaryEntry p in dic) + dic [p.Key] = ProjectCollection.Escape ((string) p.Value); + return dic; + } + + string ITaskItem2.EvaluatedIncludeEscaped { + get { return ProjectCollection.Escape (EvaluatedInclude); } + set { EvaluatedInclude = ProjectCollection.Unescape (value); } + } + + #endregion + + #region ITaskItem implementation + + IDictionary ITaskItem.CloneCustomMetadata () + { + var dic = new Hashtable (); + foreach (var md in Metadata) + dic [md.Name] = md.EvaluatedValue; + return dic; + } + + void ITaskItem.CopyMetadataTo (ITaskItem destinationItem) + { + if (destinationItem == null) + throw new ArgumentNullException ("destinationItem"); + foreach (var md in Metadata) + destinationItem.SetMetadata (md.Name, md.EvaluatedValue); + } + + string ITaskItem.GetMetadata (string metadataName) + { + return GetMetadataValue (metadataName); + } + + void ITaskItem.RemoveMetadata (string metadataName) + { + RemoveMetadata (metadataName); + } + + void ITaskItem.SetMetadata (string metadataName, string metadataValue) + { + SetMetadata (metadataName, ProjectCollection.Unescape (metadataValue)); + } + + string ITaskItem.ItemSpec { + get { return EvaluatedInclude; } + set { EvaluatedInclude = value; } + } + + int ITaskItem.MetadataCount { + get { return MetadataCount; } + } + + ICollection ITaskItem.MetadataNames { + get { return MetadataNames.ToArray (); } + } + + #endregion + } } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectMetadataInstance.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectMetadataInstance.cs index b0386fe28a..c94fe8740d 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectMetadataInstance.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectMetadataInstance.cs @@ -27,15 +27,29 @@ // using System; +using Microsoft.Build.Construction; namespace Microsoft.Build.Execution { - public class ProjectMetadataInstance - { - private ProjectMetadataInstance () - { - throw new NotImplementedException (); - } - } + public class ProjectMetadataInstance + { + internal ProjectMetadataInstance (string name, string value) + { + Name = name; + EvaluatedValue = value; + } + + public string EvaluatedValue { get; private set; } + public string Name { get; private set; } + + public ProjectMetadataInstance DeepClone () + { + return new ProjectMetadataInstance (Name, EvaluatedValue); + } + + public override string ToString () + { + return string.Format ("{0}={1}", Name, EvaluatedValue); + } + } } - diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectOnErrorInstance.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectOnErrorInstance.cs new file mode 100644 index 0000000000..b5572ef8a5 --- /dev/null +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectOnErrorInstance.cs @@ -0,0 +1,82 @@ +// +// ProjectOnErrorInstance.cs +// +// Author: +// Atsushi Enomoto (atsushi@xamarin.com) +// +// (C) 2013 Xamarin Inc. +// +// 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 Microsoft.Build.Construction; + +namespace Microsoft.Build.Execution +{ + public class ProjectOnErrorInstance : ProjectTargetInstanceChild + { + internal ProjectOnErrorInstance (ProjectOnErrorElement xml) + { + condition = xml.Condition; + ExecuteTargets = xml.ExecuteTargetsAttribute; + //this.FullPath = fullPath; + #if NET_4_5 + condition_location = xml.ConditionLocation; + ExecuteTargetsLocation = xml.ExecuteTargetsAttributeLocation; + location = xml.Location; + #endif + } + + readonly string condition; + + public override string Condition { + get { return condition; } + } + + public string ExecuteTargets { get; private set; } + + readonly ElementLocation condition_location, location; + + #if NET_4_5 + public + #else + internal + #endif + override ElementLocation ConditionLocation { + get { return condition_location; } + } + + #if NET_4_5 + public + #else + internal + #endif + ElementLocation ExecuteTargetsLocation { get; private set; } + + #if NET_4_5 + public + #else + internal + #endif + override ElementLocation Location { + get { return location; } + } + } +} + diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectPropertyGroupTaskInstance.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectPropertyGroupTaskInstance.cs new file mode 100644 index 0000000000..1e5d7fa6e3 --- /dev/null +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectPropertyGroupTaskInstance.cs @@ -0,0 +1,82 @@ +// +// ProjectPropertyGroupTaskInstance.cs +// +// Author: +// Atsushi Enomoto (atsushi@xamarin.com) +// +// (C) 2013 Xamarin Inc. +// +// 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.Generic; +using System.Linq; +using Microsoft.Build.Construction; + +namespace Microsoft.Build.Execution +{ + public sealed class ProjectPropertyGroupTaskInstance : ProjectTargetInstanceChild + { + internal ProjectPropertyGroupTaskInstance (ProjectPropertyGroupElement xml) + { + condition = xml.Condition; + condition_location = xml.ConditionLocation; + //this.FullPath = fullPath; + location = xml.Location; + + Properties = xml.Properties.Select (prop => new ProjectPropertyGroupTaskPropertyInstance (prop)).ToArray (); + } + + readonly string condition; + readonly ElementLocation condition_location, location; + + public override string Condition { + get { return condition; } + } + + #if NET_4_5 + public + #else + internal + #endif + override ElementLocation ConditionLocation { + get { return condition_location; } + } + + #if NET_4_5 + public + #else + internal + #endif + override ElementLocation Location { + get { return location; } + } + + #if NET_4_5 + public + #else + internal + #endif + ElementLocation ExecuteTargetsLocation { get; private set; } + + public ICollection<ProjectPropertyGroupTaskPropertyInstance> Properties { get; private set; } + } +} + diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectPropertyGroupTaskPropertyInstance.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectPropertyGroupTaskPropertyInstance.cs new file mode 100644 index 0000000000..df1d2c6af1 --- /dev/null +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectPropertyGroupTaskPropertyInstance.cs @@ -0,0 +1,61 @@ +// +// ProjectPropertyGroupTaskPropertyInstance.cs +// +// Author: +// Atsushi Enomoto (atsushi@xamarin.com) +// +// (C) 2013 Xamarin Inc. +// +// 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.Generic; +using System.Linq; +using Microsoft.Build.Construction; + +namespace Microsoft.Build.Execution +{ + public class ProjectPropertyGroupTaskPropertyInstance + { + internal ProjectPropertyGroupTaskPropertyInstance (ProjectPropertyElement xml) + { + Condition = xml.Condition; + Name = xml.Name; + Value = xml.Value; + #if NET_4_5 + ConditionLocation = xml.ConditionLocation; + Location = xml.Location; + #endif + } + + public string Condition { get; private set; } + + public string Name { get; private set; } + + public string Value { get; private set; } + + #if NET_4_5 + public ElementLocation ConditionLocation { get; private set; } + + public ElementLocation Location { get; private set; } + #endif + } +} + diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectPropertyInstance.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectPropertyInstance.cs index 3b6b32c2d8..5bacdbe61a 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectPropertyInstance.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectPropertyInstance.cs @@ -3,8 +3,9 @@ // // Author: // Rolf Bjarne Kvinge (rolf@xamarin.com) +// Atsushi Enomoto (atsushi@xamarin.com) // -// Copyright (C) 2011 Xamarin Inc. +// Copyright (C) 2011,2013 Xamarin Inc. // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the @@ -30,12 +31,32 @@ using System; namespace Microsoft.Build.Execution { - public class ProjectPropertyInstance - { - private ProjectPropertyInstance () - { - throw new NotImplementedException (); - } - } -} + public class ProjectPropertyInstance + { + internal ProjectPropertyInstance (string name, bool isImmutable, string evaluatedValue, Func<string> evaluatedValueGetter = null) + { + Name = name; + IsImmutable = isImmutable; + evaluated_value_getter = evaluatedValueGetter ?? (() => evaluatedValue); + } + + Func<string> evaluated_value_getter; + public string EvaluatedValue { + get { return evaluated_value_getter (); } + set { + if (IsImmutable) + throw new InvalidOperationException (); + evaluated_value_getter = () => value; + } + } + public virtual bool IsImmutable { get; private set; } + + public string Name { get; private set; } + + public override string ToString () + { + return string.Format ("{0}={1}", Name, EvaluatedValue); + } + } +} diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectTargetInstance.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectTargetInstance.cs index a95ee16cb0..3046811068 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectTargetInstance.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectTargetInstance.cs @@ -1,9 +1,11 @@ +// // ProjectTargetInstance.cs // // Author: // Rolf Bjarne Kvinge (rolf@xamarin.com) +// Atsushi Enomoto (atsshi@xamarin.com) // -// Copyright (C) 2011 Xamarin Inc. +// Copyright (C) 2011,2013 Xamarin Inc. // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the @@ -25,16 +27,83 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // + using System; +using Microsoft.Build.Construction; +using System.Collections.Generic; +using System.Linq; namespace Microsoft.Build.Execution { - public sealed class ProjectTargetInstance - { - private ProjectTargetInstance () - { - throw new NotImplementedException (); - } - } + public sealed class ProjectTargetInstance + { + internal ProjectTargetInstance (ProjectTargetElement xml) + { + FullPath = xml.ContainingProject.FullPath; + Children = xml.Children.Select<ProjectElement,ProjectTargetInstanceChild> (c => { + if (c is ProjectOnErrorElement) + return new ProjectOnErrorInstance ((ProjectOnErrorElement) c); + if (c is ProjectItemGroupElement) + return new ProjectItemGroupTaskInstance ((ProjectItemGroupElement) c); + if (c is ProjectPropertyGroupElement) + return new ProjectPropertyGroupTaskInstance ((ProjectPropertyGroupElement) c); + if (c is ProjectTaskElement) + return new ProjectTaskInstance ((ProjectTaskElement) c); + throw new NotSupportedException (); + }).ToArray (); + Condition = xml.Condition; + DependsOnTargets = xml.DependsOnTargets; + //FullPath = fullPath; + Inputs = xml.Inputs; + KeepDuplicateOutputs = xml.KeepDuplicateOutputs; + Name = xml.Name; + OnErrorChildren = xml.OnErrors.Select (c => new ProjectOnErrorInstance (c)).ToArray (); + Outputs = xml.Outputs; + Returns = xml.Returns; + Tasks = xml.Tasks.Select (t => new ProjectTaskInstance (t)).ToArray (); + AfterTargetsLocation = xml.AfterTargetsLocation; + BeforeTargetsLocation = xml.BeforeTargetsLocation; + ConditionLocation = xml.ConditionLocation; + DependsOnTargetsLocation = xml.DependsOnTargetsLocation; + InputsLocation = xml.InputsLocation; + KeepDuplicateOutputsLocation = xml.KeepDuplicateOutputsLocation; + Location = xml.Location; + OutputsLocation = xml.OutputsLocation; + ReturnsLocation = xml.ReturnsLocation; + } + + public IList<ProjectTargetInstanceChild> Children { get; private set; } + public string Condition { get; private set; } + public string DependsOnTargets { get; private set; } + public string FullPath { get; private set; } + public string Inputs { get; private set; } + public string KeepDuplicateOutputs { get; private set; } + public string Name { get; private set; } + public IList<ProjectOnErrorInstance> OnErrorChildren { get; private set; } + public string Outputs { get; private set; } + public string Returns { get; private set; } + public ICollection<ProjectTaskInstance> Tasks { get; private set; } +#if NET_4_5 + public ElementLocation AfterTargetsLocation { get; private set; } + public ElementLocation BeforeTargetsLocation { get; private set; } + public ElementLocation ConditionLocation { get; private set; } + public ElementLocation DependsOnTargetsLocation { get; private set; } + public ElementLocation InputsLocation { get; private set; } + public ElementLocation KeepDuplicateOutputsLocation { get; private set; } + public ElementLocation Location { get; private set; } + public ElementLocation OutputsLocation { get; private set; } + public ElementLocation ReturnsLocation { get; private set; } +#else + internal ElementLocation AfterTargetsLocation { get; private set; } + internal ElementLocation BeforeTargetsLocation { get; private set; } + internal ElementLocation ConditionLocation { get; private set; } + internal ElementLocation DependsOnTargetsLocation { get; private set; } + internal ElementLocation InputsLocation { get; private set; } + internal ElementLocation KeepDuplicateOutputsLocation { get; private set; } + internal ElementLocation Location { get; private set; } + internal ElementLocation OutputsLocation { get; private set; } + internal ElementLocation ReturnsLocation { get; private set; } +#endif + } } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectTargetInstanceChild.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectTargetInstanceChild.cs new file mode 100644 index 0000000000..aa38e6b006 --- /dev/null +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectTargetInstanceChild.cs @@ -0,0 +1,52 @@ +// +// ProjectTargetInstanceChild.cs +// +// Author: +// Atsushi Enomoto (atsushi@xamarin.com) +// +// (C) 2013 Xamarin Inc. +// +// 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 Microsoft.Build.Construction; + +namespace Microsoft.Build.Execution +{ + public abstract class ProjectTargetInstanceChild + { + public abstract string Condition { get; } + public string FullPath { get; internal set; } + #if NET_4_5 + public + #else + internal + #endif + abstract ElementLocation ConditionLocation { get; } + + #if NET_4_5 + public + #else + internal + #endif + abstract ElementLocation Location { get; } + } +} + diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectTaskInstance.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectTaskInstance.cs new file mode 100644 index 0000000000..bc53da1656 --- /dev/null +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectTaskInstance.cs @@ -0,0 +1,108 @@ +// +// ProjectOnErrorInstance.cs +// +// Author: +// Atsushi Enomoto (atsushi@xamarin.com) +// +// (C) 2013 Xamarin Inc. +// +// 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.Generic; +using Microsoft.Build.Construction; +using System.Linq; + +namespace Microsoft.Build.Execution +{ + public sealed class ProjectTaskInstance : ProjectTargetInstanceChild + { + internal ProjectTaskInstance (ProjectTaskElement xml) + { + condition = xml.Condition; + ContinueOnError = xml.ContinueOnError; + Name = xml.Name; + Outputs = xml.Outputs.Select (o => { + if (o.IsOutputItem) + return (ProjectTaskInstanceChild) new ProjectTaskOutputItemInstance ((ProjectOutputElement) o); + if (o.IsOutputProperty) + return new ProjectTaskOutputPropertyInstance ((ProjectOutputElement) o); + throw new NotSupportedException (); + }).ToArray (); + Parameters = new Dictionary<string,string> (xml.Parameters); + #if NET_4_5 + MSBuildArchitecture = xml.MSBuildArchitecture; + MSBuildRuntime = xml.MSBuildRuntime; + + condition_location = xml.ConditionLocation; + ContinueOnErrorLocation = xml.ContinueOnErrorLocation; + location = xml.Location; + MSBuildArchitectureLocation = xml.MSBuildArchitectureLocation; + MSBuildRuntimeLocation = xml.MSBuildRuntimeLocation; + #endif + } + + string condition; + public override string Condition { + get { return condition; } + } + + ElementLocation condition_location, location; + + #if NET_4_5 + public + #else + internal + #endif + override ElementLocation ConditionLocation { + get { return condition_location; } + } + + #if NET_4_5 + public + #else + internal + #endif + override ElementLocation Location { + get { return location; } + } + + public string ContinueOnError { get; private set; } + + #if NET_4_5 + public ElementLocation ContinueOnErrorLocation { get; private set; } + + public string MSBuildArchitecture { get; private set; } + + public ElementLocation MSBuildArchitectureLocation { get; private set; } + + public string MSBuildRuntime { get; private set; } + + public ElementLocation MSBuildRuntimeLocation { get; private set; } + #endif + + public string Name { get; private set; } + + public IList<ProjectTaskInstanceChild> Outputs { get; private set; } + + public IDictionary<string, string> Parameters { get; private set; } + } +} + diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectTaskInstanceChild.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectTaskInstanceChild.cs new file mode 100644 index 0000000000..77ea7a2525 --- /dev/null +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectTaskInstanceChild.cs @@ -0,0 +1,16 @@ +using System; +using Microsoft.Build.Construction; + +namespace Microsoft.Build.Execution +{ + public abstract class ProjectTaskInstanceChild + { + public abstract string Condition { get; } + #if NET_4_5 + public abstract ElementLocation ConditionLocation { get; } + public abstract ElementLocation Location { get; } + public abstract ElementLocation TaskParameterLocation { get; } + #endif + } +} + diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectTaskOutputItemInstance.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectTaskOutputItemInstance.cs new file mode 100644 index 0000000000..356f9a62f2 --- /dev/null +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectTaskOutputItemInstance.cs @@ -0,0 +1,42 @@ +using System; +using Microsoft.Build.Construction; + +namespace Microsoft.Build.Execution +{ + public class ProjectTaskOutputItemInstance : ProjectTaskInstanceChild + { + internal ProjectTaskOutputItemInstance (ProjectOutputElement xml) + { + condition = xml.Condition; + ItemType = xml.ItemType; + TaskParameter = xml.TaskParameter; + #if NET_4_5 + condition_location = xml.ConditionLocation; + location = xml.Location; + task_parameter_location = xml.TaskParameterLocation; + #endif + } + + public string ItemType { get; private set; } + public string TaskParameter { get; private set; } + + readonly string condition; + public override string Condition { + get { return condition; } + } + #if NET_4_5 + readonly ElementLocation condition_location, location, task_parameter_location; + public ElementLocation ItemTypeLocation { get; private set; } + public override ElementLocation ConditionLocation { + get { return condition_location; } + } + public override ElementLocation Location { + get { return location; } + } + public override ElementLocation TaskParameterLocation { + get { return task_parameter_location; } + } + #endif + } +} + diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectTaskOutputPropertyInstance.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectTaskOutputPropertyInstance.cs new file mode 100644 index 0000000000..855268e130 --- /dev/null +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectTaskOutputPropertyInstance.cs @@ -0,0 +1,43 @@ +using System; +using Microsoft.Build.Construction; + +namespace Microsoft.Build.Execution +{ + public class ProjectTaskOutputPropertyInstance : ProjectTaskInstanceChild + { + internal ProjectTaskOutputPropertyInstance (ProjectOutputElement xml) + { + condition = xml.Condition; + PropertyName = xml.PropertyName; + TaskParameter = xml.TaskParameter; + #if NET_4_5 + condition_location = xml.ConditionLocation; + location = xml.Location; + task_parameter_location = xml.TaskParameterLocation; + #endif + } + + public string PropertyName { get; private set; } + public string TaskParameter { get; private set; } + + readonly string condition; + public override string Condition { + get { return condition; } + } + + #if NET_4_5 + readonly ElementLocation condition_location, location, task_parameter_location; + public ElementLocation PropertyNameLocation { get; private set; } + public override ElementLocation ConditionLocation { + get { return condition_location; } + } + public override ElementLocation Location { + get { return location; } + } + public override ElementLocation TaskParameterLocation { + get { return task_parameter_location; } + } + #endif + } +} + diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/TargetResult.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/TargetResult.cs index 863966ba78..d6dac3a8ff 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Execution/TargetResult.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Execution/TargetResult.cs @@ -26,30 +26,40 @@ // using Microsoft.Build.Framework; - using System; +using System.Linq; +using System.Collections.Generic; namespace Microsoft.Build.Execution { - public class TargetResult : ITargetResult - { - internal TargetResult () - { - throw new NotImplementedException (); - } + public class TargetResult : ITargetResult + { + internal TargetResult () + { + } - public Exception Exception { - get { throw new NotImplementedException (); } - } + public Exception Exception { get; private set; } - public ITaskItem[] Items { - get { throw new NotImplementedException (); } - } + public ITaskItem[] Items { get; private set; } + public TargetResultCode ResultCode { get; private set; } - public TargetResultCode ResultCode { - get { throw new NotImplementedException (); } - } - } + internal void Failure (Exception exception) + { + this.Exception = exception; + ResultCode = TargetResultCode.Failure; + } + + internal void Skip () + { + ResultCode = TargetResultCode.Skipped; + } + + internal void Success (IEnumerable<ITaskItem> items) + { + Items = items.ToArray (); + ResultCode = TargetResultCode.Success; + } + } } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Internal/BuildEngine4.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Internal/BuildEngine4.cs new file mode 100644 index 0000000000..22a8c615d4 --- /dev/null +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Internal/BuildEngine4.cs @@ -0,0 +1,634 @@ +// +// BuildEngine4.cs +// +// Author: +// Atsushi Enomoto (atsushi@xamarin.com) +// +// Copyright (C) 2013 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 Microsoft.Build.Execution; +using Microsoft.Build.Framework; +using Microsoft.Build.Evaluation; +using System.Linq; +using System.IO; +using Microsoft.Build.Exceptions; +using System.Globalization; + +namespace Microsoft.Build.Internal +{ + class BuildEngine4 +#if NET_4_5 + : IBuildEngine4 +#else + : IBuildEngine3 +#endif + { + public BuildEngine4 (BuildSubmission submission) + { + this.submission = submission; + event_source = new Microsoft.Build.BuildEngine.EventSource (); + if (submission.BuildManager.OngoingBuildParameters.Loggers != null) + foreach (var l in submission.BuildManager.OngoingBuildParameters.Loggers) + l.Initialize (event_source); + } + + BuildSubmission submission; + ProjectInstance project; + ProjectTaskInstance current_task; + Microsoft.Build.BuildEngine.EventSource event_source; + + public ProjectCollection Projects { + get { return submission.BuildManager.OngoingBuildParameters.ProjectCollection; } + } + + // FIXME: + // While we are not faced to implement those features, there are some modern task execution requirements. + // + // This will have to be available for "out of process" nodes (see NodeAffinity). + // NodeAffinity is set per project file at BuildManager.HostServices. + // When NodeAffinity is set to OutOfProc, it should probably launch different build host + // that runs separate build tasks. (.NET has MSBuildTaskHost.exe which I guess is about that.) + // + // Also note that the complete implementation has to support LoadInSeparateAppDomainAttribute + // (which is most likely derived from AppDomainIsolatedBuildTask) that marks a task to run + // in separate AppDomain. + // + public void BuildProject (Func<bool> checkCancel, BuildResult result, ProjectInstance project, IEnumerable<string> targetNames, IDictionary<string,string> globalProperties, IDictionary<string,string> targetOutputs, string toolsVersion) + { + if (toolsVersion == null) + throw new ArgumentNullException ("toolsVersion"); + + var parameters = submission.BuildManager.OngoingBuildParameters; + var toolset = parameters.GetToolset (toolsVersion); + if (toolset == null) + throw new InvalidOperationException (string.Format ("Toolset version '{0}' was not resolved to valid toolset", toolsVersion)); + LogMessageEvent (new BuildMessageEventArgs (string.Format ("Using Toolset version {0}.", toolsVersion), null, null, MessageImportance.Low)); + var buildTaskFactory = new BuildTaskFactory (BuildTaskDatabase.GetDefaultTaskDatabase (toolset), submission.BuildRequest.ProjectInstance.TaskDatabase); + BuildProject (new InternalBuildArguments () { CheckCancel = checkCancel, Result = result, Project = project, TargetNames = targetNames, GlobalProperties = globalProperties, TargetOutputs = targetOutputs, ToolsVersion = toolsVersion, BuildTaskFactory = buildTaskFactory }); + } + + class InternalBuildArguments + { + public Func<bool> CheckCancel; + public BuildResult Result; + public ProjectInstance Project; + public IEnumerable<string> TargetNames; + public IDictionary<string,string> GlobalProperties; + public IDictionary<string,string> TargetOutputs; + public string ToolsVersion; + public BuildTaskFactory BuildTaskFactory; + + public void AddTargetResult (string targetName, TargetResult targetResult) + { + if (!Result.HasResultsForTarget (targetName)) + Result.AddResultsForTarget (targetName, targetResult); + } + } + + void BuildProject (InternalBuildArguments args) + { + var request = submission.BuildRequest; + var parameters = submission.BuildManager.OngoingBuildParameters; + this.project = args.Project; + + event_source.FireBuildStarted (this, new BuildStartedEventArgs ("Build Started", null, DateTime.Now)); + + try { + + var initialPropertiesFormatted = "Initial Properties:\n" + string.Join (Environment.NewLine, project.Properties.OrderBy (p => p.Name).Select (p => string.Format ("{0} = {1}", p.Name, p.EvaluatedValue)).ToArray ()); + LogMessageEvent (new BuildMessageEventArgs (initialPropertiesFormatted, null, null, MessageImportance.Low)); + var initialItemsFormatted = "Initial Items:\n" + string.Join (Environment.NewLine, project.Items.OrderBy (i => i.ItemType).Select (i => string.Format ("{0} : {1}", i.ItemType, i.EvaluatedInclude)).ToArray ()); + LogMessageEvent (new BuildMessageEventArgs (initialItemsFormatted, null, null, MessageImportance.Low)); + + // null targets -> success. empty targets -> success(!) + if (request.TargetNames == null) + args.Result.OverallResult = BuildResultCode.Success; + else { + foreach (var targetName in (args.TargetNames ?? request.TargetNames).Where (t => t != null)) + BuildTargetByName (targetName, args); + + // FIXME: check .NET behavior, whether cancellation always results in failure. + args.Result.OverallResult = args.CheckCancel () ? BuildResultCode.Failure : args.Result.ResultsByTarget.Any (p => p.Value.ResultCode == TargetResultCode.Failure) ? BuildResultCode.Failure : BuildResultCode.Success; + } + } catch (Exception ex) { + args.Result.OverallResult = BuildResultCode.Failure; + LogErrorEvent (new BuildErrorEventArgs (null, null, project.FullPath, 0, 0, 0, 0, "Unhandled exception occured during a build", null, null)); + LogMessageEvent (new BuildMessageEventArgs ("Exception details: " + ex, null, null, MessageImportance.Low)); + throw; // BuildSubmission re-catches this. + } finally { + event_source.FireBuildFinished (this, new BuildFinishedEventArgs ("Build Finished.", null, args.Result.OverallResult == BuildResultCode.Success, DateTime.Now)); + } + } + + bool BuildTargetByName (string targetName, InternalBuildArguments args) + { + var request = submission.BuildRequest; + var parameters = submission.BuildManager.OngoingBuildParameters; + ProjectTargetInstance target; + TargetResult dummyResult; + + if (args.Result.ResultsByTarget.TryGetValue (targetName, out dummyResult) && dummyResult.ResultCode == TargetResultCode.Success) { + LogMessageEvent (new BuildMessageEventArgs (string.Format ("Target '{0}' was skipped because it was already built successfully.", targetName), null, null, MessageImportance.Low)); + return true; // do not add result. + } + + var targetResult = new TargetResult (); + + // null key is allowed and regarded as blind success(!) (as long as it could retrieve target) + if (!request.ProjectInstance.Targets.TryGetValue (targetName, out target)) + throw new InvalidOperationException (string.Format ("target '{0}' was not found in project '{1}'", targetName, project.FullPath)); + else if (!args.Project.EvaluateCondition (target.Condition)) { + LogMessageEvent (new BuildMessageEventArgs (string.Format ("Target '{0}' was skipped because condition '{1}' was not met.", target.Name, target.Condition), null, null, MessageImportance.Low)); + targetResult.Skip (); + } else { + // process DependsOnTargets first. + foreach (var dep in project.ExpandString (target.DependsOnTargets).Split (';').Select (s => s.Trim ()).Where (s => !string.IsNullOrEmpty (s))) { + if (!BuildTargetByName (dep, args)) { + return false; + } + } + + Func<string,ITaskItem> creator = s => new TargetOutputTaskItem () { ItemSpec = s }; + + event_source.FireTargetStarted (this, new TargetStartedEventArgs ("Target Started", null, target.Name, project.FullPath, target.FullPath)); + try { + if (!string.IsNullOrEmpty (target.Inputs) != !string.IsNullOrEmpty (target.Outputs)) { + targetResult.Failure (new InvalidProjectFileException (target.Location, null, string.Format ("Target {0} has mismatching Inputs and Outputs specification. When one is specified, another one has to be specified too.", targetName), null, null, null)); + } else { + bool skip = false; + if (!string.IsNullOrEmpty (target.Inputs)) { + var inputs = args.Project.GetAllItems (target.Inputs, string.Empty, creator, creator, s => true, (t, s) => { + }); + if (!inputs.Any ()) { + LogMessageEvent (new BuildMessageEventArgs (string.Format ("Target '{0}' was skipped because there is no input.", target.Name), null, null, MessageImportance.Low)); + skip = true; + } else { + var outputs = args.Project.GetAllItems (target.Outputs, string.Empty, creator, creator, s => true, (t, s) => { + }); + var needsUpdates = GetOlderOutputsThanInputs (inputs, outputs).FirstOrDefault (); + if (needsUpdates != null) + LogMessageEvent (new BuildMessageEventArgs (string.Format ("Target '{0}' needs to be built because new output {1} is needed.", target.Name, needsUpdates.ItemSpec), null, null, MessageImportance.Low)); + else { + LogMessageEvent (new BuildMessageEventArgs (string.Format ("Target '{0}' was skipped because all the outputs are newer than all the inputs.", target.Name), null, null, MessageImportance.Low)); + skip = true; + } + } + } + if (skip) { + targetResult.Skip (); + } else { + if (DoBuildTarget (target, targetResult, args)) { + var items = args.Project.GetAllItems (target.Outputs, string.Empty, creator, creator, s => true, (t, s) => { + }); + targetResult.Success (items); + } + } + } + } finally { + event_source.FireTargetFinished (this, new TargetFinishedEventArgs ("Target Finished", null, targetName, project.FullPath, target.FullPath, targetResult.ResultCode != TargetResultCode.Failure)); + } + } + args.AddTargetResult (targetName, targetResult); + + return targetResult.ResultCode != TargetResultCode.Failure; + } + + IEnumerable<ITaskItem> GetOlderOutputsThanInputs (IEnumerable<ITaskItem> inputs, IEnumerable<ITaskItem> outputs) + { + return outputs.Where (o => !File.Exists (o.GetMetadata ("FullPath")) || inputs.Any (i => string.CompareOrdinal (i.GetMetadata ("LastModifiedTime"), o.GetMetadata ("LastModifiedTime")) > 0)); + } + + bool DoBuildTarget (ProjectTargetInstance target, TargetResult targetResult, InternalBuildArguments args) + { + var request = submission.BuildRequest; + + // Here we check cancellation (only after TargetStarted event). + if (args.CheckCancel ()) { + targetResult.Failure (new BuildAbortedException ("Build has canceled")); + return false; + } + + var propsToRestore = new Dictionary<string,string> (); + var itemsToRemove = new List<ProjectItemInstance> (); + try { + // Evaluate additional target properties + foreach (var c in target.Children.OfType<ProjectPropertyGroupTaskInstance> ()) { + if (!args.Project.EvaluateCondition (c.Condition)) + continue; + foreach (var p in c.Properties) { + if (!args.Project.EvaluateCondition (p.Condition)) + continue; + var value = args.Project.ExpandString (p.Value); + propsToRestore.Add (p.Name, project.GetPropertyValue (value)); + project.SetProperty (p.Name, value); + } + } + + // Evaluate additional target items + foreach (var c in target.Children.OfType<ProjectItemGroupTaskInstance> ()) { + if (!args.Project.EvaluateCondition (c.Condition)) + continue; + foreach (var item in c.Items) { + if (!args.Project.EvaluateCondition (item.Condition)) + continue; + Func<string,ProjectItemInstance> creator = i => new ProjectItemInstance (project, item.ItemType, item.Metadata.Select (m => new KeyValuePair<string,string> (m.Name, m.Value)), i); + foreach (var ti in project.GetAllItems (item.Include, item.Exclude, creator, creator, s => s == item.ItemType, (ti, s) => ti.SetMetadata ("RecurseDir", s))) + itemsToRemove.Add (ti); + } + } + + foreach (var c in target.Children.OfType<ProjectOnErrorInstance> ()) { + if (!args.Project.EvaluateCondition (c.Condition)) + continue; + throw new NotImplementedException (); + } + + // run tasks + foreach (var ti in target.Children.OfType<ProjectTaskInstance> ()) { + current_task = ti; + if (!args.Project.EvaluateCondition (ti.Condition)) { + LogMessageEvent (new BuildMessageEventArgs (string.Format ("Task '{0}' was skipped because condition '{1}' wasn't met.", ti.Name, ti.Condition), null, null, MessageImportance.Low)); + continue; + } + if (!RunBuildTask (target, ti, targetResult, args)) + return false; + } + } finally { + // restore temporary property state to the original state. + foreach (var p in propsToRestore) { + if (p.Value == string.Empty) + project.RemoveProperty (p.Key); + else + project.SetProperty (p.Key, p.Value); + } + foreach (var item in itemsToRemove) + project.RemoveItem (item); + } + return true; + } + + bool RunBuildTask (ProjectTargetInstance target, ProjectTaskInstance taskInstance, TargetResult targetResult, InternalBuildArguments args) + { + var request = submission.BuildRequest; + + var host = request.HostServices == null ? null : request.HostServices.GetHostObject (request.ProjectFullPath, target.Name, taskInstance.Name); + + // Create Task instance. + var factoryIdentityParameters = new Dictionary<string,string> (); + #if NET_4_5 + factoryIdentityParameters ["MSBuildRuntime"] = taskInstance.MSBuildRuntime; + factoryIdentityParameters ["MSBuildArchitecture"] = taskInstance.MSBuildArchitecture; + #endif + var task = args.BuildTaskFactory.CreateTask (taskInstance.Name, factoryIdentityParameters, this); + LogMessageEvent (new BuildMessageEventArgs (string.Format ("Using task {0} from {1}", taskInstance.Name, task.GetType ().AssemblyQualifiedName), null, null, MessageImportance.Low)); + task.HostObject = host; + task.BuildEngine = this; + + // Prepare task parameters. + var evaluatedTaskParams = taskInstance.Parameters.Select (p => new KeyValuePair<string,string> (p.Key, project.ExpandString (p.Value))); + + var requiredProps = task.GetType ().GetProperties () + .Where (p => p.CanWrite && p.GetCustomAttributes (typeof (RequiredAttribute), true).Any ()); + var missings = requiredProps.Where (p => !evaluatedTaskParams.Any (tp => tp.Key.Equals (p.Name, StringComparison.OrdinalIgnoreCase))); + if (missings.Any ()) + throw new InvalidOperationException (string.Format ("Task {0} of type {1} is used without specifying mandatory property: {2}", + taskInstance.Name, task.GetType (), string.Join (", ", missings.Select (p => p.Name).ToArray ()))); + + foreach (var p in evaluatedTaskParams) { + var prop = task.GetType ().GetProperty (p.Key); + if (prop == null) + throw new InvalidOperationException (string.Format ("Task {0} does not have property {1}", taskInstance.Name, p.Key)); + if (!prop.CanWrite) + throw new InvalidOperationException (string.Format ("Task {0} has property {1} but it is read-only.", taskInstance.Name, p.Key)); + if (string.IsNullOrEmpty (p.Value) && !requiredProps.Contains (prop)) + continue; + try { + var valueInstance = ConvertTo (p.Value, prop.PropertyType); + prop.SetValue (task, valueInstance, null); + } catch (Exception ex) { + throw new InvalidOperationException (string.Format ("Failed to convert '{0}' for property '{1}' of type {2}", p.Value, prop.Name, prop.PropertyType), ex); + } + } + + // Do execute task. + bool taskSuccess = false; + event_source.FireTaskStarted (this, new TaskStartedEventArgs ("Task Started", null, project.FullPath, taskInstance.FullPath, taskInstance.Name)); + try { + taskSuccess = task.Execute (); + + if (!taskSuccess) { + targetResult.Failure (null); + if (!ContinueOnError) { + return false; + } + } else { + // Evaluate task output properties and items. + foreach (var to in taskInstance.Outputs) { + if (!project.EvaluateCondition (to.Condition)) + continue; + var toItem = to as ProjectTaskOutputItemInstance; + var toProp = to as ProjectTaskOutputPropertyInstance; + string taskParameter = toItem != null ? toItem.TaskParameter : toProp.TaskParameter; + var pi = task.GetType ().GetProperty (taskParameter); + if (pi == null) + throw new InvalidOperationException (string.Format ("Task {0} does not have property {1} specified as TaskParameter", taskInstance.Name, toItem.TaskParameter)); + if (!pi.CanRead) + throw new InvalidOperationException (string.Format ("Task {0} has property {1} specified as TaskParameter, but it is write-only", taskInstance.Name, toItem.TaskParameter)); + var value = ConvertFrom (pi.GetValue (task, null)); + if (toItem != null) { + LogMessageEvent (new BuildMessageEventArgs (string.Format ("Output Item {0} from TaskParameter {1}: {2}", toItem.ItemType, toItem.TaskParameter, value), null, null, MessageImportance.Low)); + foreach (var item in value.Split (';')) + args.Project.AddItem (toItem.ItemType, item); + } else { + LogMessageEvent (new BuildMessageEventArgs (string.Format ("Output Property {0} from TaskParameter {1}: {2}", toProp.PropertyName, toProp.TaskParameter, value), null, null, MessageImportance.Low)); + args.Project.SetProperty (toProp.PropertyName, value); + } + } + } + } finally { + event_source.FireTaskFinished (this, new TaskFinishedEventArgs ("Task Finished", null, project.FullPath, taskInstance.FullPath, taskInstance.Name, taskSuccess)); + } + return true; + } + + object ConvertTo (string source, Type targetType) + { + if (targetType == typeof(ITaskItem) || targetType.IsSubclassOf (typeof(ITaskItem))) + return new TargetOutputTaskItem () { ItemSpec = WindowsCompatibilityExtensions.NormalizeFilePath (source.Trim ()) }; + if (targetType.IsArray) + return new ArrayList (source.Split (';').Select (s => s.Trim ()).Where (s => !string.IsNullOrEmpty (s)).Select (s => ConvertTo (s, targetType.GetElementType ())).ToArray ()) + .ToArray (targetType.GetElementType ()); + if (targetType == typeof(bool)) { + switch (source != null ? source.ToLower (CultureInfo.InvariantCulture) : string.Empty) { + case "true": + case "yes": + case "on": + return true; + case "false": + case "no": + case "off": + case "": + return false; + } + } + return Convert.ChangeType (source == "" ? null : source, targetType); + } + + string ConvertFrom (object source) + { + if (source == null) + return string.Empty; + if (source is ITaskItem) + return ((ITaskItem) source).ItemSpec; + if (source.GetType ().IsArray) + return string.Join (";", ((Array) source).Cast<object> ().Select (o => ConvertFrom (o)).ToArray ()); + else + return (string) Convert.ChangeType (source, typeof (string)); + } + + class TargetOutputTaskItem : ITaskItem2 + { + Hashtable metadata = new Hashtable (); + + #region ITaskItem2 implementation + public string GetMetadataValueEscaped (string metadataName) + { + return ProjectCollection.Escape ((string) metadata [metadataName]); + } + public void SetMetadataValueLiteral (string metadataName, string metadataValue) + { + metadata [metadataName] = ProjectCollection.Unescape (metadataValue); + } + public IDictionary CloneCustomMetadataEscaped () + { + var ret = new Hashtable (); + foreach (DictionaryEntry e in metadata) + ret [e.Key] = ProjectCollection.Escape ((string) e.Value); + return ret; + } + public string EvaluatedIncludeEscaped { + get { return ProjectCollection.Escape (ItemSpec); } + set { ItemSpec = ProjectCollection.Unescape (value); } + } + #endregion + #region ITaskItem implementation + public IDictionary CloneCustomMetadata () + { + return new Hashtable (metadata); + } + public void CopyMetadataTo (ITaskItem destinationItem) + { + foreach (DictionaryEntry e in metadata) + destinationItem.SetMetadata ((string) e.Key, (string) e.Value); + } + public string GetMetadata (string metadataName) + { + var wk = ProjectCollection.GetWellKnownMetadata (metadataName, ItemSpec, Path.GetFullPath, null); + if (wk != null) + return wk; + return (string) metadata [metadataName]; + } + public void RemoveMetadata (string metadataName) + { + metadata.Remove (metadataName); + } + public void SetMetadata (string metadataName, string metadataValue) + { + metadata [metadataName] = metadataValue; + } + public string ItemSpec { get; set; } + public int MetadataCount { + get { return metadata.Count; } + } + public ICollection MetadataNames { + get { return metadata.Keys; } + } + #endregion + } + +#if NET_4_5 + #region IBuildEngine4 implementation + + // task objects are not in use anyways though... + + class TaskObjectRegistration + { + public TaskObjectRegistration (object key, object obj, RegisteredTaskObjectLifetime lifetime, bool allowEarlyCollection) + { + Key = key; + Object = obj; + Lifetime = lifetime; + AllowEarlyCollection = allowEarlyCollection; + } + public object Key { get; private set; } + public object Object { get; private set; } + public RegisteredTaskObjectLifetime Lifetime { get; private set; } + public bool AllowEarlyCollection { get; private set; } + } + + List<TaskObjectRegistration> task_objects = new List<TaskObjectRegistration> (); + + public object GetRegisteredTaskObject (object key, RegisteredTaskObjectLifetime lifetime) + { + var reg = task_objects.FirstOrDefault (t => t.Key == key && t.Lifetime == lifetime); + return reg != null ? reg.Object : null; + } + + public void RegisterTaskObject (object key, object obj, RegisteredTaskObjectLifetime lifetime, bool allowEarlyCollection) + { + task_objects.Add (new TaskObjectRegistration (key, obj, lifetime, allowEarlyCollection)); + } + + public object UnregisterTaskObject (object key, RegisteredTaskObjectLifetime lifetime) + { + var reg = task_objects.FirstOrDefault (t => t.Key == key && t.Lifetime == lifetime); + if (reg != null) + task_objects.Remove (reg); + return reg.Object; + } + #endregion +#endif + + #region IBuildEngine3 implementation + + public BuildEngineResult BuildProjectFilesInParallel (string[] projectFileNames, string[] targetNames, IDictionary[] globalProperties, IList<string>[] removeGlobalProperties, string[] toolsVersion, bool returnTargetOutputs) + { + throw new NotImplementedException (); + } + + public void Reacquire () + { + throw new NotImplementedException (); + } + + public void Yield () + { + throw new NotImplementedException (); + } + + #endregion + + #region IBuildEngine2 implementation + + public bool BuildProjectFile (string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs, string toolsVersion) + { + var proj = GetProjectInstance (projectFileName, toolsVersion); + var globalPropertiesThatMakeSense = new Dictionary<string,string> (); + foreach (DictionaryEntry p in globalProperties) + globalPropertiesThatMakeSense [(string) p.Key] = (string) p.Value; + var result = new BuildResult (); + var outputs = new Dictionary<string, string> (); + BuildProject (() => false, result, proj, targetNames, globalPropertiesThatMakeSense, outputs, toolsVersion); + foreach (var p in outputs) + targetOutputs [p.Key] = p.Value; + return result.OverallResult == BuildResultCode.Success; + } + + public bool BuildProjectFilesInParallel (string[] projectFileNames, string[] targetNames, IDictionary[] globalProperties, IDictionary[] targetOutputsPerProject, string[] toolsVersion, bool useResultsCache, bool unloadProjectsOnCompletion) + { + throw new NotImplementedException (); + } + + public bool IsRunningMultipleNodes { + get { + throw new NotImplementedException (); + } + } + + ProjectInstance GetProjectInstance (string projectFileName, string toolsVersion) + { + string fullPath = Path.GetFullPath (projectFileName); + if (submission.BuildRequest.ProjectFullPath == fullPath) + return submission.BuildRequest.ProjectInstance; + // FIXME: could also be filtered by global properties + // http://msdn.microsoft.com/en-us/library/microsoft.build.evaluation.projectcollection.getloadedprojects.aspx + var project = Projects.GetLoadedProjects (projectFileName).FirstOrDefault (p => p.ToolsVersion == toolsVersion); + if (project == null) + throw new InvalidOperationException (string.Format ("Project '{0}' is not loaded", projectFileName)); + return submission.BuildManager.GetProjectInstanceForBuild (project); + } + + #endregion + + #region IBuildEngine implementation + + public bool BuildProjectFile (string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs) + { + return BuildProjectFile (projectFileName, targetNames, globalProperties, targetOutputs, Projects.DefaultToolsVersion); + } + + public void LogCustomEvent (CustomBuildEventArgs e) + { + event_source.FireCustomEventRaised (this, e); + } + + public void LogErrorEvent (BuildErrorEventArgs e) + { + event_source.FireErrorRaised (this, e); + } + + public void LogMessageEvent (BuildMessageEventArgs e) + { + event_source.FireMessageRaised (this, e); + } + + public void LogWarningEvent (BuildWarningEventArgs e) + { + event_source.FireWarningRaised (this, e); + } + + public int ColumnNumberOfTaskNode { + get { return current_task.Location != null ? current_task.Location.Column : 0; } + } + + public bool ContinueOnError { + get { return current_task != null && project.EvaluateCondition (current_task.Condition) && EvaluateContinueOnError (current_task.ContinueOnError); } + } + + bool EvaluateContinueOnError (string value) + { + switch (value) { + case "WarnAndContinue": + case "ErrorAndContinue": + return true; + case "ErrorAndStop": + return false; + } + // empty means "stop on error", so don't pass empty string to EvaluateCondition(). + return !string.IsNullOrEmpty (value) && project.EvaluateCondition (value); + } + + public int LineNumberOfTaskNode { + get { return current_task.Location != null ? current_task.Location.Line : 0; } + } + + public string ProjectFileOfTaskNode { + get { return current_task.FullPath; } + } + + #endregion + } +} + diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Internal/BuildNodeManager.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Internal/BuildNodeManager.cs new file mode 100644 index 0000000000..ebfad5ba75 --- /dev/null +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Internal/BuildNodeManager.cs @@ -0,0 +1,209 @@ +// +// BuildNodeManager.cs +// +// Author: +// Atsushi Enomoto (atsushi@xamarin.com) +// +// Copyright (C) 2013 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.Generic; +using System.Linq; +using Microsoft.Build.Execution; +using Microsoft.Build.Framework; +using System.Threading.Tasks; +using System.Threading; +using System.Collections.Concurrent; + +namespace Microsoft.Build.Internal +{ + class BuildNodeManager + { + public BuildNodeManager (BuildManager buildManager) + { + BuildManager = buildManager; + new Thread (RunLoop).Start (); + } + + ~BuildNodeManager () + { + run_loop = false; + queue_wait_handle.Set (); + } + + public BuildManager BuildManager { get; private set; } + + List<BuildNode> in_proc_nodes = new List<BuildNode> (); + List<BuildNode> out_proc_nodes = new List<BuildNode> (); + AutoResetEvent queue_wait_handle = new AutoResetEvent (false); + ConcurrentQueue<BuildSubmission> queued_builds = new ConcurrentQueue<BuildSubmission> (); + // FIXME: currently it is not in use but it should be stored somewhere for cancellation. + Dictionary<BuildSubmission,Task> ongoing_builds = new Dictionary<BuildSubmission, Task> (); + bool run_loop = true; + + readonly TaskFactory<BuildResult> task_factory = new TaskFactory<BuildResult> (); + internal TaskFactory<BuildResult> ThreadTaskFactory { + get { return task_factory; } + } + + void RunLoop () + { + while (run_loop) { + try { + if (queued_builds.Count == 0) + queue_wait_handle.WaitOne (); + if (!run_loop) + break; + BuildSubmission build; + if (!queued_builds.TryDequeue (out build)) + continue; + StartOneBuild (build); + } catch (Exception ex) { + // FIXME: I guess INodeLogger should be used instead. + Console.Error.WriteLine ("Uncaught build node exception occured"); + Console.Error.WriteLine (ex); + } + } + } + + public void Stop () + { + run_loop = false; + queue_wait_handle.Set (); + } + + public void ResetCaches () + { + in_proc_nodes.Clear (); + out_proc_nodes.Clear (); + } + + public void Enqueue (BuildSubmission build) + { + queued_builds.Enqueue (build); + queue_wait_handle.Set (); + } + + void StartOneBuild (BuildSubmission build) + { + var node = TakeNode (build); + // FIXME: Task (non-generic) here causes NotImplementedException in somewhere in Interlocked. It does not make sense. + ongoing_builds [build] = task_factory.StartNew (node.ExecuteBuild); + //new Thread (() => { node.ExecuteBuild (); }).Start (); + } + + void EndOneBuild (BuildNode node) + { + var task = ongoing_builds [node.Build]; + ongoing_builds [node.Build] = null; + node.Release (); + } + + // FIXME: take max nodes into account here, and get throttling working. + BuildNode TakeNode (BuildSubmission build) + { + var host = BuildManager.OngoingBuildParameters.HostServices; + NodeAffinity affinity; + if (host == null) + affinity = NodeAffinity.Any; + else + affinity = host.GetNodeAffinity (build.BuildRequest.ProjectFullPath); + BuildNode n = GetReusableNode (affinity); + if (n != null) + n.Assign (build); + else { + n = new BuildNode (this, affinity == NodeAffinity.Any ? NodeAffinity.InProc : affinity); + n.Assign (build); + if (n.Affinity == NodeAffinity.InProc) + in_proc_nodes.Add (n); + else + out_proc_nodes.Add (n); + } + return n; + } + + BuildNode GetReusableNode (NodeAffinity affinity) + { + if (!BuildManager.OngoingBuildParameters.EnableNodeReuse) + return null; + + if (affinity != NodeAffinity.OutOfProc) + foreach (var n in in_proc_nodes) + if (n.IsAvailable && (n.Affinity & affinity) != 0) + return n; + if (affinity != NodeAffinity.InProc) + foreach (var n in out_proc_nodes) + if (n.IsAvailable && (n.Affinity & affinity) != 0) + return n; + return null; + } + + internal class BuildNode + { + static Random rnd = new Random (); + + public BuildNode (BuildNodeManager manager, NodeAffinity affinity) + { + Manager = manager; + Affinity = affinity; + Id = rnd.Next (); + } + + public bool IsAvailable { get; private set; } + public int Id { get; private set; } + public BuildNodeManager Manager { get; set; } + public NodeAffinity Affinity { get; private set; } + public BuildSubmission Build { get; private set; } + + public void Assign (BuildSubmission build) + { + IsAvailable = false; + Build = build; + } + + public void Release () + { + Build = null; + IsAvailable = true; + } + + public BuildResult ExecuteBuild () + { + BuildResult result; + try { + // FIXME: depending on NodeAffinity, build it through another MSBuild process. + if (Affinity == NodeAffinity.OutOfProc) + throw new NotImplementedException (); + result = Build.InternalExecute (); + } catch (Exception ex) { + // FIXME: I guess INodeLogger should be used instead. + Console.Error.WriteLine ("Uncaught build node exception occured"); + Console.Error.WriteLine (ex); + result = null; + } finally { + Manager.EndOneBuild (this); + } + return result; + } + } + } +} diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Internal/BuildTaskDatabase.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Internal/BuildTaskDatabase.cs new file mode 100644 index 0000000000..0b7660b2d4 --- /dev/null +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Internal/BuildTaskDatabase.cs @@ -0,0 +1,138 @@ +// +// BuildTaskFactory.cs +// +// Author: +// Atsushi Enomoto (atsushi@xamarin.com) +// +// Copyright (C) 2013 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.Generic; +using System.Linq; +using Microsoft.Build.Framework; +using System.Reflection; +using Microsoft.Build.Execution; +using Microsoft.Build.Evaluation; +using Microsoft.Build.Construction; +using System.IO; +using System.Xml; + +namespace Microsoft.Build.Internal +{ + class BuildTaskDatabase + { + const string default_tasks_file = "Microsoft.Common.tasks"; + static readonly Dictionary<string,BuildTaskDatabase> default_factory = new Dictionary<string, BuildTaskDatabase> (); + + public static BuildTaskDatabase GetDefaultTaskDatabase (Toolset toolset) + { + if (toolset == null) + throw new ArgumentNullException ("toolset"); + BuildTaskDatabase defaults; + if (!default_factory.TryGetValue (toolset.ToolsVersion, out defaults)) { + defaults = new BuildTaskDatabase (toolset); + } + return defaults; + } + + // for 'default' tasks. + BuildTaskDatabase (Toolset toolset) + { + ProjectRootElement root; + using (var xml = XmlReader.Create (Path.Combine (toolset.ToolsPath, default_tasks_file))) + root = ProjectRootElement.Create (xml); + LoadUsingTasks (null, root); + } + + public BuildTaskDatabase (ProjectInstance projectInstance, ProjectRootElement projectRootElement) + { + LoadUsingTasks (projectInstance, projectRootElement); + } + + internal class TaskDescription + { + public TaskAssembly TaskAssembly { get; set; } + public string Name { get; set; } + public Type TaskFactoryType { get; set; } + public Type TaskType { get; set; } + public IDictionary<string, TaskPropertyInfo> TaskFactoryParameters { get; set; } + public string TaskBody { get; set; } + + public bool IsMatch (string name) + { + int ridx = Name.LastIndexOf ('.'); + int tidx = name.IndexOf ('.'); + return string.Equals (Name, name, StringComparison.OrdinalIgnoreCase) || + tidx < 0 && ridx > 0 && string.Equals (Name.Substring (ridx + 1), name, StringComparison.OrdinalIgnoreCase); + } + } + + internal class TaskAssembly + { + public string AssemblyName { get; set; } + public string AssemblyFile { get; set; } + public Assembly LoadedAssembly { get; set; } + } + + readonly List<TaskAssembly> assemblies = new List<TaskAssembly> (); + readonly List<TaskDescription> task_descs = new List<TaskDescription> (); + + public List<TaskDescription> Tasks { + get { return task_descs; } + } + + void LoadUsingTasks (ProjectInstance projectInstance, ProjectRootElement project) + { + Func<string,bool> cond = s => projectInstance != null ? projectInstance.EvaluateCondition (s) : Convert.ToBoolean (s); + foreach (var ut in project.UsingTasks) { + var ta = assemblies.FirstOrDefault (a => a.AssemblyFile.Equals (ut.AssemblyFile, StringComparison.OrdinalIgnoreCase) || a.AssemblyName.Equals (ut.AssemblyName, StringComparison.OrdinalIgnoreCase)); + if (ta == null) { + ta = new TaskAssembly () { AssemblyName = ut.AssemblyName, AssemblyFile = ut.AssemblyFile }; + ta.LoadedAssembly = ta.AssemblyName != null ? Assembly.Load (ta.AssemblyName) : Assembly.LoadFile (ta.AssemblyFile); + assemblies.Add (ta); + } + var pg = ut.ParameterGroup == null ? null : ut.ParameterGroup.Parameters.Select (p => new TaskPropertyInfo (p.Name, Type.GetType (p.ParameterType), cond (p.Output), cond (p.Required))) + .ToDictionary (p => p.Name); + var task = new TaskDescription () { + TaskAssembly = ta, + Name = ut.TaskName, + TaskFactoryType = string.IsNullOrEmpty (ut.TaskFactory) ? null : LoadTypeFrom (ta.LoadedAssembly, ut.TaskName, ut.TaskFactory), + TaskType = string.IsNullOrEmpty (ut.TaskFactory) ? LoadTypeFrom (ta.LoadedAssembly, ut.TaskName, ut.TaskName) : null, + TaskFactoryParameters = pg, + TaskBody = ut.TaskBody != null && cond (ut.TaskBody.Condition) ? ut.TaskBody.Evaluate : null, + }; + task_descs.Add (task); + } + } + + Type LoadTypeFrom (Assembly a, string taskName, string possiblyShortTypeName) + { + Type type = a.GetType (possiblyShortTypeName, false, true); + if (possiblyShortTypeName.IndexOf ('.') < 0) + type = a.GetTypes ().FirstOrDefault (t => t.Name == possiblyShortTypeName); + if (type == null) + throw new InvalidOperationException (string.Format ("For task '{0}' Specified type '{1}' was not found in assembly '{2}'", taskName, possiblyShortTypeName, a.FullName)); + return type; + } + } +} + diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Internal/BuildTaskFactory.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Internal/BuildTaskFactory.cs new file mode 100644 index 0000000000..c587b35136 --- /dev/null +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Internal/BuildTaskFactory.cs @@ -0,0 +1,81 @@ +// BuildTaskFactory.cs +// +// Author: +// Atsushi Enomoto (atsushi@xamarin.com) +// +// Copyright (C) 2013 Xamarin Inc. +// +// 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.Threading; +using System.Threading.Tasks; +using System.Linq; +using Microsoft.Build.Framework; +using Microsoft.Build.Internal; +using System.Collections.Generic; +using Microsoft.Build.Execution; + +namespace Microsoft.Build.Internal +{ + class BuildTaskFactory + { + public BuildTaskFactory (BuildTaskDatabase builtInDatabase, BuildTaskDatabase perProjectDatabase) + { + this.built_in_database = builtInDatabase; + this.per_project_database = perProjectDatabase; + } + + readonly BuildTaskDatabase built_in_database, per_project_database; + readonly List<ITaskFactory> task_factories = new List<ITaskFactory> (); + + public void ResetCaches () + { + task_factories.Clear (); + } + + public ITask CreateTask (string name, IDictionary<string,string> factoryIdentityParameters, IBuildEngine engine) + { + Func<BuildTaskDatabase.TaskDescription,bool> fn = t => t.IsMatch (name); + var td = per_project_database.Tasks.FirstOrDefault (fn) ?? built_in_database.Tasks.FirstOrDefault (fn); + if (td == null) + throw new InvalidOperationException (string.Format ("Task '{0}' could not be found", name)); + if (td.TaskFactoryType != null) { + var tf = task_factories.FirstOrDefault (f => f.GetType () == td.TaskFactoryType); + if (tf == null) { + tf = (ITaskFactory) Activator.CreateInstance (td.TaskFactoryType); +#if NET_4_5 + var tf2 = tf as ITaskFactory2; + if (tf2 != null) + tf2.Initialize (name, factoryIdentityParameters, td.TaskFactoryParameters, td.TaskBody, engine); + else +#endif + tf.Initialize (name, td.TaskFactoryParameters, td.TaskBody, engine); + task_factories.Add (tf); + } + return tf.CreateTask (engine); + } + else + return (ITask) Activator.CreateInstance (td.TaskType); + } + } +} + diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionConstructs.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionConstructs.cs new file mode 100644 index 0000000000..358f58e925 --- /dev/null +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionConstructs.cs @@ -0,0 +1,197 @@ +// +// ExpressionConstructs.cs +// +// Author: +// Atsushi Enomoto (atsushi@xamarin.com) +// +// Copyright (C) 2013 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; + +namespace Microsoft.Build.Internal.Expressions +{ + + class Locatable + { + public ILocation Location { get; set; } + } + + partial class ExpressionList : ILocation, IEnumerable<Expression> + { + public ExpressionList () + { + } + + public ExpressionList (Expression entry) + { + Add (entry); + } + + public int Count { + get { return list.Count; } + } + + //public int Line { + // get { return list.Count == 0 ? 0 : list [0].Line; } + //} + public int Column { + get { return list.Count == 0 ? 0 : list [0].Column; } + } + public string File { + get { return list.Count == 0 ? null : list [0].File; } + } + public string ToLocationString () + { + return list.Count == 0 ? null : list [0].Location.ToLocationString (); + } + + public IEnumerator<Expression> GetEnumerator () + { + return list.GetEnumerator (); + } + + IEnumerator IEnumerable.GetEnumerator () + { + return list.GetEnumerator (); + } + + List<Expression> list = new List<Expression> (); + + public ExpressionList Add (Expression expr) + { + list.Add (expr); + return this; + } + + public ExpressionList Insert (int pos, Expression expr) + { + list.Insert (pos, expr); + return this; + } + } + + abstract partial class Expression : Locatable, ILocation + { + //public int Line { + // get { return Location.Line; } + //} + public int Column { + get { return Location.Column; } + } + public string File { + get { return Location.File; } + } + public string ToLocationString () + { + return Location.ToLocationString (); + } + } + + enum Operator + { + EQ, + NE, + LT, + LE, + GT, + GE, + And, + Or + } + + partial class BinaryExpression : Expression + { + public Operator Operator { get; set; } + public Expression Left { get; set; } + public Expression Right { get; set; } + } + + partial class BooleanLiteral : Expression + { + public bool Value { get; set; } + } + + partial class NotExpression : Expression + { + public Expression Negated { get; set; } + } + + partial class PropertyAccessExpression : Expression + { + public PropertyAccess Access { get; set; } + } + + enum PropertyTargetType + { + Object, + Type, + } + + class PropertyAccess : Locatable + { + public NameToken Name { get; set; } + public Expression Target { get; set; } + public PropertyTargetType TargetType { get; set; } + public ExpressionList Arguments { get; set; } + } + + partial class ItemAccessExpression : Expression + { + public ItemApplication Application { get; set; } + } + + class ItemApplication : Locatable + { + public NameToken Name { get; set; } + public ExpressionList Expressions { get; set; } + } + + partial class MetadataAccessExpression : Expression + { + public MetadataAccess Access { get; set; } + } + + class MetadataAccess : Locatable + { + public NameToken Metadata { get; set; } + public NameToken ItemType { get; set; } + } + + partial class StringLiteral : Expression + { + public NameToken Value { get; set; } + } + + partial class RawStringLiteral : Expression + { + public NameToken Value { get; set; } + } + + partial class FunctionCallExpression : Expression + { + public NameToken Name { get; set; } + public ExpressionList Arguments { get; set; } + } +} + diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionEvaluator.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionEvaluator.cs new file mode 100644 index 0000000000..4aa9b259f2 --- /dev/null +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionEvaluator.cs @@ -0,0 +1,521 @@ +// +// ExpressionEvaluator.cs +// +// Author: +// Atsushi Enomoto (atsushi@xamarin.com) +// +// Copyright (C) 2013 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.Linq; +using Microsoft.Build.Evaluation; +using Microsoft.Build.Exceptions; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.Build.Execution; +using Microsoft.Build.Framework; +using System.IO; + +namespace Microsoft.Build.Internal.Expressions +{ + class ExpressionEvaluator + { + public ExpressionEvaluator (Project project, string replacementForMissingPropertyAndItem) + { + ReplacementForMissingPropertyAndItem = replacementForMissingPropertyAndItem; + Project = project; + /* + GetItems = (name) => project.GetItems (name).Select (i => new KeyValuePair<string,string> (i.ItemType, i.EvaluatedInclude)); + GetProperty = (name) => { + var prop = project.GetProperty (name); + return new KeyValuePair<string,string> (prop != null ? prop.Name : null, prop != null ? prop.EvaluatedValue : null); + }; + */ + } + + public ExpressionEvaluator (ProjectInstance project, string replacementForMissingPropertyAndItem) + { + ReplacementForMissingPropertyAndItem = replacementForMissingPropertyAndItem; + ProjectInstance = project; + /* + GetItems = (name) => project.GetItems (name).Select (i => new KeyValuePair<string,string> (i.ItemType, i.EvaluatedInclude)); + GetProperty = (name) => { + var prop = project.GetProperty (name); + return new KeyValuePair<string,string> (prop != null ? prop.Name : null, prop != null ? prop.EvaluatedValue : null); + }; + */ + } + + EvaluationContext CreateContext (string source) + { + return new EvaluationContext (source, this); + } + + public Project Project { get; private set; } + public ProjectInstance ProjectInstance { get; set; } + //public Func<string,IEnumerable<KeyValuePair<string,string>>> GetItems { get; private set; } + //public Func<string,KeyValuePair<string,string>> GetProperty { get; private set; } + + public string ReplacementForMissingPropertyAndItem { get; set; } + + // it is to prevent sequential property value expansion in boolean expression + public string Wrapper { + get { return ReplacementForMissingPropertyAndItem != null ? "'" : null; } + } + + public string Evaluate (string source) + { + return Evaluate (source, new ExpressionParserManual (source ?? string.Empty, ExpressionValidationType.LaxString).Parse ()); + } + + string Evaluate (string source, ExpressionList exprList) + { + if (exprList == null) + throw new ArgumentNullException ("exprList"); + return string.Concat (exprList.Select (e => e.EvaluateAsString (CreateContext (source)))); + } + + public bool EvaluateAsBoolean (string source) + { + try { + var el = new ExpressionParser ().Parse (source, ExpressionValidationType.StrictBoolean); + if (el.Count () != 1) + throw new InvalidProjectFileException ("Unexpected number of tokens: " + el.Count ()); + return el.First ().EvaluateAsBoolean (CreateContext (source)); + } catch (yyParser.yyException ex) { + throw new InvalidProjectFileException (string.Format ("failed to evaluate expression as boolean: '{0}': {1}", source, ex.Message), ex); + } + } + } + + class EvaluationContext + { + public EvaluationContext (string source, ExpressionEvaluator evaluator) + { + Source = source; + Evaluator = evaluator; + } + + public string Source { get; private set; } + + public ExpressionEvaluator Evaluator { get; private set; } + public object ContextItem { get; set; } + + Stack<object> evaluating_items = new Stack<object> (); + Stack<object> evaluating_props = new Stack<object> (); + + public IEnumerable<object> GetItems (string name) + { + if (Evaluator.Project != null) + return Evaluator.Project.GetItems (name); + else + return Evaluator.ProjectInstance.GetItems (name); + } + + public IEnumerable<object> GetAllItems () + { + if (Evaluator.Project != null) + return Evaluator.Project.AllEvaluatedItems; + else + return Evaluator.ProjectInstance.AllEvaluatedItems; + } + + public string EvaluateItem (string itemType, object item) + { + if (evaluating_items.Contains (item)) + throw new InvalidProjectFileException (string.Format ("Recursive reference to item '{0}' was found", itemType)); + try { + evaluating_items.Push (item); + var eval = item as ProjectItem; + if (eval != null) + return Evaluator.Evaluate (eval.EvaluatedInclude); + else + return Evaluator.Evaluate (((ProjectItemInstance) item).EvaluatedInclude); + } finally { + evaluating_items.Pop (); + } + } + + public string EvaluateProperty (string name) + { + if (Evaluator.Project != null) { + var prop = Evaluator.Project.GetProperty (name); + if (prop == null) + return null; + return EvaluateProperty (prop, prop.Name, prop.EvaluatedValue); + } else { + var prop = Evaluator.ProjectInstance.GetProperty (name); + if (prop == null) + return null; + return EvaluateProperty (prop, prop.Name, prop.EvaluatedValue); + } + } + + public string EvaluateProperty (object prop, string name, string value) + { + if (evaluating_props.Contains (prop)) + throw new InvalidProjectFileException (string.Format ("Recursive reference to property '{0}' was found", name)); + try { + evaluating_props.Push (prop); + // FIXME: needs verification on whether string evaluation is appropriate or not. + return Evaluator.Evaluate (value); + } finally { + evaluating_props.Pop (); + } + } + } + + abstract partial class Expression + { + public abstract string EvaluateAsString (EvaluationContext context); + public abstract bool EvaluateAsBoolean (EvaluationContext context); + public abstract object EvaluateAsObject (EvaluationContext context); + + public bool EvaluateStringAsBoolean (EvaluationContext context, string ret) + { + if (ret != null) { + if (ret.Equals ("TRUE", StringComparison.InvariantCultureIgnoreCase)) + return true; + else if (ret.Equals ("FALSE", StringComparison.InvariantCultureIgnoreCase)) + return false; + } + throw new InvalidProjectFileException (this.Location, string.Format ("Condition '{0}' is evaluated as '{1}' and cannot be converted to boolean", context.Source, ret)); + } + } + + partial class BinaryExpression : Expression + { + public override bool EvaluateAsBoolean (EvaluationContext context) + { + switch (Operator) { + case Operator.EQ: + return string.Equals (Left.EvaluateAsString (context), Right.EvaluateAsString (context), StringComparison.OrdinalIgnoreCase); + case Operator.NE: + return !string.Equals (Left.EvaluateAsString (context), Right.EvaluateAsString (context), StringComparison.OrdinalIgnoreCase); + case Operator.And: + case Operator.Or: + // evaluate first, to detect possible syntax error on right expr. + var lb = Left.EvaluateAsBoolean (context); + var rb = Right.EvaluateAsBoolean (context); + return Operator == Operator.And ? (lb && rb) : (lb || rb); + } + // comparison expressions - evaluate comparable first, then compare values. + var left = Left.EvaluateAsObject (context); + var right = Right.EvaluateAsObject (context); + if (!(left is IComparable && right is IComparable)) + throw new InvalidProjectFileException ("expression cannot be evaluated as boolean"); + var result = ((IComparable) left).CompareTo (right); + switch (Operator) { + case Operator.GE: + return result >= 0; + case Operator.GT: + return result > 0; + case Operator.LE: + return result <= 0; + case Operator.LT: + return result < 0; + } + throw new InvalidOperationException (); + } + + public override object EvaluateAsObject (EvaluationContext context) + { + throw new NotImplementedException (); + } + + static readonly Dictionary<Operator,string> strings = new Dictionary<Operator, string> () { + {Operator.EQ, " == "}, + {Operator.NE, " != "}, + {Operator.LT, " < "}, + {Operator.LE, " <= "}, + {Operator.GT, " > "}, + {Operator.GE, " >= "}, + {Operator.And, " And "}, + {Operator.Or, " Or "}, + }; + + public override string EvaluateAsString (EvaluationContext context) + { + return Left.EvaluateAsString (context) + strings [Operator] + Right.EvaluateAsString (context); + } + } + + partial class BooleanLiteral : Expression + { + public override string EvaluateAsString (EvaluationContext context) + { + return Value ? "True" : "False"; + } + + public override bool EvaluateAsBoolean (EvaluationContext context) + { + return Value; + } + + public override object EvaluateAsObject (EvaluationContext context) + { + return Value; + } + } + + partial class NotExpression : Expression + { + public override string EvaluateAsString (EvaluationContext context) + { + // no negation for string + return "!" + Negated.EvaluateAsString (context); + } + + public override bool EvaluateAsBoolean (EvaluationContext context) + { + return !Negated.EvaluateAsBoolean (context); + } + + public override object EvaluateAsObject (EvaluationContext context) + { + return EvaluateAsString (context); + } + } + + partial class PropertyAccessExpression : Expression + { + public override bool EvaluateAsBoolean (EvaluationContext context) + { + var ret = EvaluateAsString (context); + return EvaluateStringAsBoolean (context, ret); + } + + public override string EvaluateAsString (EvaluationContext context) + { + var ret = EvaluateAsObject (context); + // FIXME: this "wrapper" is kind of hack, to prevent sequential property references such as $(X)$(Y). + return ret == null ? context.Evaluator.ReplacementForMissingPropertyAndItem : context.Evaluator.Wrapper + ret.ToString () + context.Evaluator.Wrapper; + } + + public override object EvaluateAsObject (EvaluationContext context) + { + try { + return DoEvaluateAsObject (context); + } catch (TargetInvocationException ex) { + throw new InvalidProjectFileException ("Access to property caused an error", ex); + } + } + + object DoEvaluateAsObject (EvaluationContext context) + { + if (Access.Target == null) { + return context.EvaluateProperty (Access.Name.Name); + } else { + if (this.Access.TargetType == PropertyTargetType.Object) { + var obj = Access.Target.EvaluateAsObject (context); + if (obj == null) + return null; + if (Access.Arguments != null) { + var args = Access.Arguments.Select (e => e.EvaluateAsObject (context)).ToArray (); + var method = FindMethod (obj.GetType (), Access.Name.Name, args); + if (method == null) + throw new InvalidProjectFileException (Location, string.Format ("access to undefined method '{0}' of '{1}' at {2}", Access.Name.Name, Access.Target.EvaluateAsString (context), Location)); + return method.Invoke (obj, AdjustArgsForCall (method, args)); + } else { + var prop = obj.GetType ().GetProperty (Access.Name.Name); + if (prop == null) + throw new InvalidProjectFileException (Location, string.Format ("access to undefined property '{0}' of '{1}' at {2}", Access.Name.Name, Access.Target.EvaluateAsString (context), Location)); + return prop.GetValue (obj, null); + } + } else { + var type = Type.GetType (Access.Target.EvaluateAsString (context)); + if (type == null) + throw new InvalidProjectFileException (Location, string.Format ("specified type '{0}' was not found", Access.Target.EvaluateAsString (context))); + if (Access.Arguments != null) { + var args = Access.Arguments.Select (e => e.EvaluateAsObject (context)).ToArray (); + var method = FindMethod (type, Access.Name.Name, args); + if (method == null) + throw new InvalidProjectFileException (Location, string.Format ("access to undefined static method '{0}' of '{1}' at {2}", Access.Name.Name, Access.Target.EvaluateAsString (context), Location)); + return method.Invoke (null, AdjustArgsForCall (method, args)); + } else { + var prop = type.GetProperty (Access.Name.Name); + if (prop == null) + throw new InvalidProjectFileException (Location, string.Format ("access to undefined static property '{0}' of '{1}' at {2}", Access.Name.Name, Access.Target.EvaluateAsString (context), Location)); + return prop.GetValue (null, null); + } + } + } + } + + MethodInfo FindMethod (Type type, string name, object [] args) + { + var methods = type.GetMethods ().Where (m => { + if (m.Name != name) + return false; + var pl = m.GetParameters (); + if (pl.Length == args.Length) + return true; + // calling String.Format() with either set of arguments is valid: + // - three strings (two for varargs) + // - two strings (happen to be exact match) + // - one string (no varargs) + if (pl.Length > 0 && pl.Length - 1 <= args.Length && + pl.Last ().GetCustomAttributesData ().Any (a => a.Constructor.DeclaringType == typeof (ParamArrayAttribute))) + return true; + return false; + }); + if (methods.Count () == 1) + return methods.First (); + return args.Any (a => a == null) ? + type.GetMethod (name) : + type.GetMethod (name, args.Select (o => o.GetType ()).ToArray ()); + } + + object [] AdjustArgsForCall (MethodInfo m, object[] args) + { + if (m.GetParameters ().Length == args.Length + 1) + return args.Concat (new object[] {Array.CreateInstance (m.GetParameters ().Last ().ParameterType.GetElementType (), 0)}).ToArray (); + else + return args; + } + } + + partial class ItemAccessExpression : Expression + { + public override bool EvaluateAsBoolean (EvaluationContext context) + { + return EvaluateStringAsBoolean (context, EvaluateAsString (context)); + } + + public override string EvaluateAsString (EvaluationContext context) + { + string itemType = Application.Name.Name; + var items = context.GetItems (itemType); + if (!items.Any ()) + return context.Evaluator.ReplacementForMissingPropertyAndItem; + if (Application.Expressions == null) + return string.Join (";", items.Select (item => context.EvaluateItem (itemType, item))); + else + return string.Join (";", items.Select (item => { + context.ContextItem = item; + var ret = string.Concat (Application.Expressions.Select (e => e.EvaluateAsString (context))); + context.ContextItem = null; + return ret; + })); + } + + public override object EvaluateAsObject (EvaluationContext context) + { + return EvaluateAsString (context); + } + } + + partial class MetadataAccessExpression : Expression + { + public override bool EvaluateAsBoolean (EvaluationContext context) + { + return EvaluateStringAsBoolean (context, EvaluateAsString (context)); + } + + public override string EvaluateAsString (EvaluationContext context) + { + string itemType = this.Access.ItemType != null ? this.Access.ItemType.Name : null; + string metadataName = Access.Metadata.Name; + IEnumerable<object> items; + if (this.Access.ItemType != null) + items = context.GetItems (itemType); + else if (context.ContextItem != null) + items = new Object [] { context.ContextItem }; + else + items = context.GetAllItems (); + + var values = items.Select (i => (i is ProjectItem) ? ((ProjectItem) i).GetMetadataValue (metadataName) : ((ProjectItemInstance) i).GetMetadataValue (metadataName)).Where (s => !string.IsNullOrEmpty (s)); + return string.Join (";", values); + } + + public override object EvaluateAsObject (EvaluationContext context) + { + return EvaluateAsString (context); + } + } + partial class StringLiteral : Expression + { + public override bool EvaluateAsBoolean (EvaluationContext context) + { + var ret = EvaluateAsString (context); + return EvaluateStringAsBoolean (context, ret); + } + + public override string EvaluateAsString (EvaluationContext context) + { + return context.Evaluator.Evaluate (this.Value.Name); + } + + public override object EvaluateAsObject (EvaluationContext context) + { + return EvaluateAsString (context); + } + } + partial class RawStringLiteral : Expression + { + public override string EvaluateAsString (EvaluationContext context) + { + return Value.Name; + } + + public override bool EvaluateAsBoolean (EvaluationContext context) + { + throw new InvalidProjectFileException ("raw string literal cannot be evaluated as boolean"); + } + + public override object EvaluateAsObject (EvaluationContext context) + { + return EvaluateAsString (context); + } + } + + partial class FunctionCallExpression : Expression + { + public override string EvaluateAsString (EvaluationContext context) + { + throw new NotImplementedException (); + } + + public override bool EvaluateAsBoolean (EvaluationContext context) + { + if (string.Equals (Name.Name, "Exists", StringComparison.OrdinalIgnoreCase)) { + if (Arguments.Count != 1) + throw new InvalidProjectFileException (Location, "Function 'Exists' expects 1 argument"); + string val = Arguments.First ().EvaluateAsString (context); + return Directory.Exists (val) || System.IO.File.Exists (val); + } + if (string.Equals (Name.Name, "HasTrailingSlash", StringComparison.OrdinalIgnoreCase)) { + if (Arguments.Count != 1) + throw new InvalidProjectFileException (Location, "Function 'HasTrailingSlash' expects 1 argument"); + string val = Arguments.First ().EvaluateAsString (context); + return val.LastOrDefault () == '\\' || val.LastOrDefault () == '/'; + } + throw new InvalidProjectFileException (Location, string.Format ("Unsupported function '{0}'", Name)); + } + + public override object EvaluateAsObject (EvaluationContext context) + { + throw new NotImplementedException (); + } + } +} + diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionParser.jay b/mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionParser.jay new file mode 100644 index 0000000000..200c96ba83 --- /dev/null +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionParser.jay @@ -0,0 +1,264 @@ +// +// ExpressionParser.jay +// +// Author: +// Atsushi Enomoto (atsushi@xamarin.com) +// +// Copyright (C) 2013 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.Text; +using Microsoft.Build.Evaluation; +using Microsoft.Build.Exceptions; +using Microsoft.Build.Framework; + +/* + +Pseudo formal syntax for .NET 4.0 expression: + +Condition = Expression +Include = Expression* + + Expression + BooleanLiteral + TrueLiteral + FalseLiteral + BinaryExpression + Expression "==" Expression + Expression "!=" Expression + Expression ">" Expression + Expression ">=" Expression + Expression "<" Expression + Expression "<=" Expression + Expression "And" Expression + Expression "Or" Expression + UnaryExpression + "!" Expression + PropertyExpression + "$(" PropertyApplication ")" + ItemExpression + "@(" ItemApplication ")" + MetadataBatchingExpression + "%(" MetadataBatchingApplication ")" + StringLiteralOrFunction + StringLiteralOrFunctionName ( "(" FunctionArguments ")" )? + +.NET error messages are so detailed which is something like "you forgot '(' after '$' ?" - so +it is likely that the MS tokenizer is hand-written. + +*/ + +namespace Microsoft.Build.Internal.Expressions +{ + class ExpressionParser + { + static readonly int yacc_verbose_flag = Environment.GetEnvironmentVariable ("MONO_MSBUILD_PARSER_DEBUG") == "1" ? 1 : 0; + + object debug_obj = yacc_verbose_flag == 0 ? null : new yydebug.yyDebugSimple (); + + public ExpressionList Parse (string source, ExpressionValidationType validationType) + { + var tokenizer = new ExpressionTokenizer (source, validationType); + return (ExpressionList) yyparse (tokenizer, debug_obj); + } + + BinaryExpression Binary (Operator op, object left, object right) + { + return new BinaryExpression () { Operator = op, Left = (Expression) left, Right = (Expression) right, Location = (ILocation) left }; + } +%} + +%token TRUE_LITERAL +%token FALSE_LITERAL +%token STRING_LITERAL +%token EQ // == +%token NE // != +%token GT // > +%token GE // >= +%token LT // < +%token LE // <= +%token AND // AND +%token OR // OR +%token NOT //! +%token DOT //. +%token COMMA //, +%token PROP_OPEN // $( +%token ITEM_OPEN // @( +%token METADATA_OPEN // %( +%token PAREN_OPEN // ( +%token PAREN_CLOSE // ) +%token BRACE_OPEN // [ +%token BRACE_CLOSE // ] +%token COLON2 // :: +%token ARROW // -> +%token NAME +%token ERROR + +%start ExpressionList + +%% + +ExpressionList + : /* empty */ + { $$ = new ExpressionList (); } + | ExpressionList Expression + { $$ = ((ExpressionList) $1).Add ((Expression) $2); } + ; + +Expression + : LogicalExpression + ; + +LogicalExpression + : ComparisonExpression + | LogicalExpression AND LogicalExpression + { $$ = Binary (Operator.And, $1, $3); } + | LogicalExpression OR LogicalExpression + { $$ = Binary (Operator.Or, $1, $3); } + ; + +ComparisonExpression + : UnaryExpression + | UnaryExpression EQ UnaryExpression + { $$ = Binary (Operator.EQ, $1, $3); } + | UnaryExpression NE UnaryExpression + { $$ = Binary (Operator.NE, $1, $3); } + | UnaryExpression GT UnaryExpression + { $$ = Binary (Operator.GT, $1, $3); } + | UnaryExpression GE UnaryExpression + { $$ = Binary (Operator.GE, $1, $3); } + | UnaryExpression LT UnaryExpression + { $$ = Binary (Operator.LT, $1, $3); } + | UnaryExpression LE UnaryExpression + { $$ = Binary (Operator.LE, $1, $3); } + ; + +UnaryExpression + : PrimaryExpression + | NOT UnaryExpression + { $$ = new NotExpression () { Negated = (Expression) $2, Location = (ILocation) $1 }; } + ; + +PrimaryExpression + : BooleanLiteral + | StringLiteral + | UnaryExpression + | PropertyAccessExpression + | ItemAccessExpression + | MetadataAccessExpression + | RawStringLiteralOrFunction + | ParenthesizedExpression + ; + +BooleanLiteral + : TRUE_LITERAL + { $$ = new BooleanLiteral () { Value = true, Location = (ILocation) $1 }; } + | FALSE_LITERAL + { $$ = new BooleanLiteral () { Value = false, Location = (ILocation) $1 }; } + ; + +PropertyAccessExpression + : PROP_OPEN PropertyAccess PAREN_CLOSE + { $$ = new PropertyAccessExpression () { Access = (PropertyAccess) $2, Location = (ILocation) $1 }; } + ; + +PropertyAccess + : NAME + { $$ = new PropertyAccess () { Name = (NameToken) $1, TargetType = PropertyTargetType.Object, Location = (NameToken) $1 }; } + | Expression DOT NAME + { $$ = new PropertyAccess () { Name = (NameToken) $3, Target = (Expression) $1, TargetType = PropertyTargetType.Object, Location = (ILocation) $1 }; } + | BRACE_OPEN QualifiedNameExpression BRACE_CLOSE COLON2 NAME + { $$ = new PropertyAccess () { Name = (NameToken) $5, Target = (Expression) $2, TargetType = PropertyTargetType.Type, Location = (ILocation) $1 }; } + | BRACE_OPEN QualifiedNameExpression BRACE_CLOSE COLON2 NAME PAREN_OPEN FunctionCallArguments PAREN_CLOSE + { $$ = new PropertyAccess () { Name = (NameToken) $5, Target = (Expression) $2, TargetType = PropertyTargetType.Type, Arguments = (ExpressionList) $7, Location = (ILocation) $1 }; } + ; + +QualifiedNameExpression + : QualifiedName + { $$ = new StringLiteral () { Value = (NameToken) $1, Location = (ILocation) $1 }; } + ; + +QualifiedName + : NAME + | QualifiedName DOT NAME + { $$ = new NameToken () { Name = ((NameToken) $1).Name + "." + ((NameToken) $3).Name, Column = ((ILocation) $1).Column }; } + ; + +ItemAccessExpression + : ITEM_OPEN ItemApplication PAREN_CLOSE + { $$ = new ItemAccessExpression () { Application = (ItemApplication) $2, Location = (ILocation) $1 }; } + ; + +// looking a bit messy, but gives different location +ItemApplication + : NAME + { $$ = new ItemApplication () { Name = (NameToken) $1, Location = (ILocation) $1 }; } + | NAME ARROW ExpressionList + { $$ = new ItemApplication () { Name = (NameToken) $1, Expressions = (ExpressionList) $3, Location = (ILocation) $1 }; } + ; + +MetadataAccessExpression + : METADATA_OPEN MetadataAccess PAREN_CLOSE + { $$ = new MetadataAccessExpression () { Access = (MetadataAccess) $2, Location = (ILocation) $1 }; } + ; + +// looking a bit messy, but gives different location +MetadataAccess + : NAME + { $$ = new MetadataAccess () { Metadata = (NameToken) $1, Location = (ILocation) $1 }; } + | NAME DOT NAME + { $$ = new MetadataAccess () { ItemType = (NameToken) $1, Metadata = (NameToken) $3, Location = (ILocation) $1 }; } + ; + +StringLiteral + : STRING_LITERAL + { $$ = new StringLiteral () { Value = (NameToken) $1, Location = (ILocation) $1 }; } + ; + +RawStringLiteralOrFunction + : NAME + { $$ = new RawStringLiteral () { Value = (NameToken) $1, Location = (ILocation) $1 }; } + | NAME PAREN_OPEN PAREN_CLOSE + { $$ = new FunctionCallExpression () { Name = (NameToken) $1, Arguments = new ExpressionList (), Location = (ILocation) $1 }; } + | NAME PAREN_OPEN FunctionCallArguments PAREN_CLOSE + { $$ = new FunctionCallExpression () { Name = (NameToken) $1, Arguments = (ExpressionList) $3, Location = (ILocation) $1 }; } + ; + +FunctionCallArguments + : /* empty */ + { $$ = new ExpressionList (); } + | Expression + { $$ = new ExpressionList ().Add ((Expression) $1); } + | FunctionCallArguments COMMA Expression + { $$ = ((ExpressionList) $1).Add ((Expression) $3); } + ; + +ParenthesizedExpression + : PAREN_OPEN Expression PAREN_CLOSE + { $$ = (Expression) $2; } + ; + +%% + + } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionParserManual.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionParserManual.cs new file mode 100644 index 0000000000..f177505587 --- /dev/null +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionParserManual.cs @@ -0,0 +1,271 @@ +// +// ExpressionParserManual.cs +// +// Author: +// Atsushi Enomoto (atsushi@xamarin.com) +// +// Copyright (C) 2013 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.Generic; +using System.Linq; +using Microsoft.Build.Exceptions; + +namespace Microsoft.Build.Internal.Expressions +{ + class ExpressionParserManual + { + // FIXME: we are going to not need ExpressionValidationType for this; always LaxString. + public ExpressionParserManual (string source, ExpressionValidationType validationType) + { + if (source == null) + throw new ArgumentNullException ("source"); + this.source = source; + validation_type = validationType; + } + + string source; + ExpressionValidationType validation_type; + + public ExpressionList Parse () + { + return Parse (0, source.Length); + } + + ExpressionList Parse (int start, int end) + { + if (string.IsNullOrWhiteSpace (source)) + return new ExpressionList (); + + var ret = new ExpressionList (); + while (start < end) { + int bak = start; + ret.Add (ParseSingle (ref start, end)); + SkipSpaces (ref start); + if (bak == start) + throw new Exception ("Parser failed to progress token position: " + source); + } + return ret; + } + + static readonly char [] token_starters = "$@%(),".ToCharArray (); + + Expression ParseSingle (ref int start, int end) + { + char token = source [start]; + switch (token) { + case '$': + case '@': + case '%': + if (start == end || start + 1 == source.Length || source [start + 1] != '(') { + if (validation_type == ExpressionValidationType.StrictBoolean) + throw new InvalidProjectFileException (string.Format ("missing '(' after '{0}' at {1} in \"{2}\"", source [start], start, source)); + else + goto default; // treat as raw literal to the section end + } + start += 2; + int last = FindMatchingCloseParen (start, end); + if (last < 0) { + if (validation_type == ExpressionValidationType.StrictBoolean) + throw new InvalidProjectFileException (string.Format ("expression did not have matching ')' since index {0} in \"{1}\"", start, source)); + else { + start -= 2; + goto default; // treat as raw literal to the section end + } + } + Expression ret; + if (token == '$') + ret = EvaluatePropertyExpression (start, last); + else if (token == '%') + ret = EvaluateMetadataExpression (start, last); + else + ret = EvaluateItemExpression (start, last); + start = last + 1; + return ret; + + // Below (until default) are important only for Condition evaluation + case '(': + if (validation_type == ExpressionValidationType.LaxString) + goto default; + start++; + last = FindMatchingCloseParen (start, end); + if (last < 0) { + if (validation_type == ExpressionValidationType.StrictBoolean) + throw new InvalidProjectFileException (string.Format ("expression did not have matching ')' since index {0} in \"{1}\"", start, source)); + else { + start--; + goto default; // treat as raw literal to the section end + } + } + var contents = Parse (start, last).ToArray (); + if (contents.Length > 1) + throw new InvalidProjectFileException (string.Format ("unexpected continuous expression within (){0} in \"{1}\"", contents [1].Column > 0 ? " at " + contents [1].Column : null, source)); + return contents.First (); + + default: + int idx = source.IndexOfAny (token_starters, start + 1); + string name = idx < 0 ? source.Substring (start, end - start) : source.Substring (start, idx - start); + var val = new NameToken () { Name = name }; + ret = new RawStringLiteral () { Value = val }; + if (idx >= 0) + start = idx; + else + start = end; + + return ret; + } + } + + int FindMatchingCloseParen (int start, int end) + { + int n = 0; + for (int i = start; i < end; i++) { + if (source [i] == '(') + n++; + else if (source [i] == ')') { + if (n-- == 0) + return i; + } + } + return -1; // invalid + } + + static readonly string spaces = " \t\r\n"; + + void SkipSpaces (ref int start) + { + while (start < source.Length && spaces.Contains (source [start])) + start++; + } + + PropertyAccessExpression EvaluatePropertyExpression (int start, int end) + { + // member access + int dotAt = source.LastIndexOf ('.', end, end - start); + int colonsAt = source.LastIndexOf ("::", end, end - start, StringComparison.Ordinal); + if (dotAt < 0 && colonsAt < 0) { + // property access without member specification + int parenAt = source.IndexOf ('(', start, end - start); + string name = parenAt < 0 ? source.Substring (start, end - start) : source.Substring (start, parenAt - start); + var access = new PropertyAccess () { + Name = new NameToken () { Name = name }, + TargetType = PropertyTargetType.Object + }; + if (parenAt > 0) { // method arguments + start = parenAt + 1; + access.Arguments = ParseFunctionArguments (ref start, end); + } + return new PropertyAccessExpression () { Access = access }; + } + if (colonsAt < 0 || colonsAt < dotAt) { + // property access with member specification + int mstart = dotAt + 1; + int parenAt = source.IndexOf ('(', mstart, end - mstart); + string name = parenAt < 0 ? source.Substring (mstart, end - mstart) : source.Substring (mstart, parenAt - mstart); + var access = new PropertyAccess () { + Name = new NameToken () { Name = name }, + TargetType = PropertyTargetType.Object, + Target = dotAt < 0 ? null : Parse (start, dotAt).FirstOrDefault () + }; + if (parenAt > 0) { // method arguments + start = parenAt + 1; + access.Arguments = ParseFunctionArguments (ref start, end); + } + return new PropertyAccessExpression () { Access = access }; + } else { + // static type access + string type = source.Substring (start, colonsAt - start); + if (type.Length < 2 || type [0] != '[' || type [type.Length - 1] != ']') + throw new InvalidProjectFileException (string.Format ("Static function call misses appropriate type name surrounded by '[' and ']' at {0} in \"{1}\"", start, source)); + type = type.Substring (1, type.Length - 2); + start = colonsAt + 2; + int parenAt = source.IndexOf ('(', start, end - start); + string member = parenAt < 0 ? source.Substring (start, end - start) : source.Substring (start, parenAt - start); + if (member.Length == 0) + throw new InvalidProjectFileException ("Static member name is missing"); + var access = new PropertyAccess () { + Name = new NameToken () { Name = member }, + TargetType = PropertyTargetType.Type, + Target = new StringLiteral () { Value = new NameToken () { Name = type } } + }; + if (parenAt > 0) { // method arguments + start = parenAt + 1; + access.Arguments = ParseFunctionArguments (ref start, end); + } + return new PropertyAccessExpression () { Access = access }; + } + } + + ExpressionList ParseFunctionArguments (ref int start, int end) + { + var args = new ExpressionList (); + do { + SkipSpaces (ref start); + if (start == source.Length) + throw new InvalidProjectFileException ("unterminated function call arguments."); + if (source [start] == ')') + break; + else if (args.Any ()) { + if (source [start] != ',') + throw new InvalidProjectFileException (string.Format ("invalid function call arguments specification. ',' is expected, got '{0}'", source [start])); + start++; + } + args.Add (ParseSingle (ref start, end)); + } while (true); + start++; + return args; + } + + ItemAccessExpression EvaluateItemExpression (int start, int end) + { + // using property as context and evaluate + int idx = source.IndexOf ("->", start, StringComparison.Ordinal); + if (idx > 0) { + string name = source.Substring (start, idx - start); + return new ItemAccessExpression () { + Application = new ItemApplication () { + Name = new NameToken () { Name = name }, + Expressions = Parse (idx, end - idx) + } + }; + } else { + string name = source.Substring (start, end - start); + return new ItemAccessExpression () { + Application = new ItemApplication () { Name = new NameToken () { Name = name } } + }; + } + } + + MetadataAccessExpression EvaluateMetadataExpression (int start, int end) + { + int idx = source.IndexOf ('.', start, end - start); + string item = idx < 0 ? null : source.Substring (start, idx); + string meta = idx < 0 ? source.Substring (start, end - start) : source.Substring (idx + 1, end - idx - 1); + var access = new MetadataAccess () { + ItemType = item == null ? null : new NameToken () { Column = start, Name = item }, + Metadata = new NameToken () { Column = idx < 0 ? start : idx + 1, Name = meta } + }; + return new MetadataAccessExpression () { Access = access }; + } + } +} + diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionTokenizer.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionTokenizer.cs new file mode 100644 index 0000000000..9760ad2acb --- /dev/null +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionTokenizer.cs @@ -0,0 +1,309 @@ +// +// ExpressionTokenizer.cs +// +// Author: +// Atsushi Enomoto (atsushi@xamarin.com) +// +// Copyright (C) 2013 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.Generic; +using Microsoft.Build.Evaluation; + +namespace Microsoft.Build.Internal.Expressions +{ + enum ExpressionValidationType + { + LaxString, + StrictBoolean, + } + + enum TokenizerMode + { + Default, + InsideItemOrProperty, + } + + class ExpressionTokenizer : yyParser.yyInput + { + public ExpressionTokenizer (string source, ExpressionValidationType validationType) + { + this.source = source; + current_token_position = -1; + validation_type = validationType; + modes.Push (TokenizerMode.Default); + } + + string source; + ExpressionValidationType validation_type; + + int current_token; + string error; + int pos, current_token_position; + object token_value; + Stack<TokenizerMode> modes = new Stack<TokenizerMode> (); + + TokenizerMode CurrentTokenizerMode { + get { return modes.Peek (); } + } + + public bool advance () + { + if (pos == source.Length) + return false; + + error = null; + token_value = null; + + while (pos < source.Length) { + if (spaces.IndexOf (source [pos]) >= 0) + pos++; + else + break; + } + if (pos == source.Length) + return false; + current_token_position = pos; + + switch (source [pos++]) { + case '.': + TokenForItemPropertyValue (".", Token.DOT); + break; + case ',': + TokenForItemPropertyValue (",", Token.COMMA); + break; + case '[': + TokenForItemPropertyValue ("[", Token.BRACE_OPEN); + break; + case ']': + TokenForItemPropertyValue ("]", Token.BRACE_CLOSE); + break; + case '(': + modes.Push (TokenizerMode.Default); + TokenForItemPropertyValue ("(", Token.PAREN_OPEN); + break; + case ')': + if (modes.Count > 0) { + modes.Pop (); + current_token = Token.PAREN_CLOSE; + } else { + token_value = ")"; + current_token = Token.NAME; + } + break; + case '-': + if (pos < source.Length && source [pos] == '>') { + current_token = Token.ARROW; + pos++; + } else + ErrorOnStrictBoolean ("-", "'-' is not followed by '>'."); + break; + case '=': + if (pos < source.Length && source [pos] == '=') { + current_token = Token.EQ; + pos++; + } else + ErrorOnStrictBoolean ("=", "'=' is not followed by '='."); + break; + case ':': + if (pos < source.Length && source [pos] == ':') { + current_token = Token.COLON2; + } else + ErrorOnStrictBoolean (":", "':' is not followed by ':'."); + pos++; + break; + case '!': + if (pos < source.Length && source [pos] == '=') { + pos++; + current_token = Token.NE; + } else + TokenForItemPropertyValue ("!", Token.NOT); + break; + case '>': + if (pos < source.Length && source [pos] == '=') { + pos++; + current_token = Token.GE; + } else + current_token = Token.GT; + break; + case '<': + if (pos < source.Length && source [pos] == '=') { + pos++; + current_token = Token.LE; + } else + current_token = Token.LT; + break; + case '$': + if (pos < source.Length && source [pos] == '(') { + modes.Push (TokenizerMode.InsideItemOrProperty); + current_token = Token.PROP_OPEN; + pos++; + } else + ErrorOnStrictBoolean ("$", "property reference '$' is not followed by '('."); + break; + case '@': + if (pos < source.Length && source [pos] == '(') { + modes.Push (TokenizerMode.InsideItemOrProperty); + current_token = Token.ITEM_OPEN; + pos++; + } else + ErrorOnStrictBoolean ("@", "item reference '@' is not followed by '('."); + break; + case '%': + if (pos < source.Length && source [pos] == '(') { + modes.Push (TokenizerMode.InsideItemOrProperty); + current_token = Token.METADATA_OPEN; + pos++; + } else + ErrorOnStrictBoolean ("%", "metadata reference '%' is not followed by '('."); + break; + case '"': + case '\'': + pos = source.IndexOf (source [pos - 1], pos); + if (pos < 0) { + ErrorOnStrictBoolean ("'", "unterminated string literal"); + pos = source.Length; + } + token_value = source.Substring (current_token_position + 1, pos - current_token_position - 1); + current_token = Token.STRING_LITERAL; + pos++; + break; + default: + pos = source.IndexOfAny (token_starter_chars, pos); + if (pos < 0) + pos = source.Length; + var val = source.Substring (current_token_position, pos - current_token_position); + if (val.Equals ("AND", StringComparison.OrdinalIgnoreCase)) + current_token = Token.AND; + else if (val.Equals ("OR", StringComparison.OrdinalIgnoreCase)) + current_token = Token.OR; + else if (val.Equals ("TRUE", StringComparison.OrdinalIgnoreCase)) + current_token = Token.TRUE_LITERAL; + else if (val.Equals ("FALSE", StringComparison.OrdinalIgnoreCase)) + current_token = Token.FALSE_LITERAL; + else if (val.Equals ("YES", StringComparison.OrdinalIgnoreCase)) + current_token = Token.TRUE_LITERAL; + else if (val.Equals ("NO", StringComparison.OrdinalIgnoreCase)) + current_token = Token.FALSE_LITERAL; + else if (val.Equals ("ON", StringComparison.OrdinalIgnoreCase)) + current_token = Token.TRUE_LITERAL; + else if (val.Equals ("OFF", StringComparison.OrdinalIgnoreCase)) + current_token = Token.FALSE_LITERAL; + else { + current_token = Token.NAME; + token_value = ProjectCollection.Unescape (val); + break; + } + break; + } + return true; + } + string spaces = " \t\r\n"; + + static readonly char [] token_starter_chars = ".,[]()-=:!><$@%\"' ".ToCharArray (); + + void ReadStringLiteral (string source, char c) + { + while (pos < source.Length && source [pos] != c) + pos++; + if (source [pos - 1] != c) + ErrorOnStrictBoolean (c.ToString (), string.Format ("missing string literal terminator [{0}]", c)); + else { + current_token = Token.NAME; + token_value = source.Substring (current_token_position + 1, pos - current_token_position - 2); + token_value = ProjectCollection.Unescape ((string) token_value); + } + } + + void TokenForItemPropertyValue (string value, int token) + { + if (true)//CurrentTokenizerMode == TokenizerMode.InsideItemOrProperty) + current_token = token; + else { + current_token = Token.NAME; + token_value = value; + } + } + + void ErrorOnStrictBoolean (string value, string message) + { + if (validation_type == ExpressionValidationType.StrictBoolean) { + current_token = Token.ERROR; + error = message; + } else { + current_token = Token.NAME; + token_value = value; + } + } + + public int token () + { + return current_token; + } + + public object value () + { + if (current_token == Token.NAME || current_token == Token.STRING_LITERAL) + return new NameToken () { Name = (string) token_value, Column = current_token_position }; + else if (error != null) + return new ErrorToken () { Message = error, Column = current_token_position }; + else + return new Location () { Column = current_token_position }; + } + } + + class NameToken : Location + { + public string Name { get; set; } + + public override string ToString () + { + return string.Format ("[NameToken: Value={0}]", Name); + } + } + + class ErrorToken : Location + { + public string Message { get; set; } + } + + interface ILocation + { + //int Line { get; } + int Column { get; } + string File { get; } + + string ToLocationString (); + } + + class Location : ILocation + { + //public int Line { get; set; } + public int Column { get; set; } + public string File { get; set; } + + public string ToLocationString () + { + return "at " + Column; + } + } +} diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Internal/ProjectTaskItem.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Internal/ProjectTaskItem.cs new file mode 100644 index 0000000000..98d4bb2a58 --- /dev/null +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Internal/ProjectTaskItem.cs @@ -0,0 +1,92 @@ +// +// ProjectTaskItem.cs +// +// Author: +// Atsushi Enomoto (atsushi@xamarin.com) +// +// Copyright (C) 2013 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.Linq; +using Microsoft.Build.Framework; +using Microsoft.Build.Construction; +using Microsoft.Build.Evaluation; +using System.IO; + +namespace Microsoft.Build.Internal +{ + class ProjectTaskItem : ITaskItem + { + ProjectItemElement item; + string evaluated_include_part; + + public ProjectTaskItem (ProjectItemElement item, string evaluatedIncludePart) + { + this.item = item; + this.evaluated_include_part = WindowsCompatibilityExtensions.NormalizeFilePath (evaluatedIncludePart); + } + #region ITaskItem implementation + System.Collections.IDictionary ITaskItem.CloneCustomMetadata () + { + var ret = new System.Collections.Hashtable (); + foreach (var p in item.Metadata) + ret [p.Name] = p; + return ret; + } + void ITaskItem.CopyMetadataTo (ITaskItem destinationItem) + { + throw new NotImplementedException (); + } + string ITaskItem.GetMetadata (string metadataName) + { + var wk = ProjectCollection.GetWellKnownMetadata (metadataName, evaluated_include_part, Path.GetFullPath, null); + if (wk != null) + return wk; + var mde = item.Metadata.FirstOrDefault (m => m.Name == metadataName); + return mde != null ? mde.Value : null; + } + void ITaskItem.RemoveMetadata (string metadataName) + { + throw new NotImplementedException (); + } + void ITaskItem.SetMetadata (string metadataName, string metadataValue) + { + throw new NotImplementedException (); + } + string ITaskItem.ItemSpec { + get { return evaluated_include_part; } + set { throw new NotImplementedException (); } + } + int ITaskItem.MetadataCount { + get { + throw new NotImplementedException (); + } + } + System.Collections.ICollection ITaskItem.MetadataNames { + get { + throw new NotImplementedException (); + } + } + #endregion + } +} + diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Logging/FileLogger.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Internal/WindowsCompatibilityExtensions.cs index 331d29ab9b..38f9d6b29e 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Logging/FileLogger.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Internal/WindowsCompatibilityExtensions.cs @@ -1,9 +1,10 @@ -// FileLogger.cs +// +// WindowsCompatibilityExtensions.cs // // Author: -// Rolf Bjarne Kvinge (rolf@xamarin.com) +// Atsushi Enomoto (atsushi@xamarin.com) // -// Copyright (C) 2011 Xamarin Inc. +// (C) 2013 Xamarin Inc. // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the @@ -24,13 +25,17 @@ // 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.IO; -namespace Microsoft.Build.Logging +namespace Microsoft.Build.Internal { - public class FileLogger - { - } + static class WindowsCompatibilityExtensions + { + public static string NormalizeFilePath (string path) + { + return path.Replace ('\\', Path.DirectorySeparatorChar); + } + } } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Logging/ConfigurableForwardingLogger.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Logging/ConfigurableForwardingLogger.cs new file mode 100644 index 0000000000..077a1bb4ef --- /dev/null +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Logging/ConfigurableForwardingLogger.cs @@ -0,0 +1,43 @@ +using System; +using Microsoft.Build.Framework; + +namespace Microsoft.Build.Logging +{ + public class ConfigurableForwardingLogger : IForwardingLogger + { + #region INodeLogger implementation + + public void Initialize (IEventSource eventSource, int nodeCount) + { + Initialize (eventSource); + } + + #endregion + + #region ILogger implementation + + public void Initialize (IEventSource eventSource) + { + throw new NotImplementedException (); + } + + public void Shutdown () + { + throw new NotImplementedException (); + } + + public string Parameters { get; set; } + + public LoggerVerbosity Verbosity { get; set; } + + #endregion + + #region IForwardingLogger implementation + + public IEventRedirector BuildEventRedirector { get; set; } + + public int NodeId { get; set; } + + #endregion + } +} diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Logging/ConsoleLogger.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Logging/ConsoleLogger.cs deleted file mode 100644 index 161476c2c0..0000000000 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Logging/ConsoleLogger.cs +++ /dev/null @@ -1,89 +0,0 @@ -// ConsoleLogger.cs -// -// Author: -// Rolf Bjarne Kvinge (rolf@xamarin.com) -// -// Copyright (C) 2011 Xamarin Inc. -// -// 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 Microsoft.Build.Framework; - -using System; - -namespace Microsoft.Build.Logging -{ - public class ConsoleLogger : INodeLogger - { - public ConsoleLogger () - : this (LoggerVerbosity.Normal) - { - } - - public ConsoleLogger (LoggerVerbosity verbosity) - { - throw new NotImplementedException (); - } - - public ConsoleLogger (LoggerVerbosity verbosity, WriteHandler write, ColorSetter colorSet, ColorResetter colorReset) - { - throw new NotImplementedException (); - } - - #region INodeLogger implementation - public void Initialize (IEventSource eventSource, int nodeCount) - { - throw new NotImplementedException (); - } - #endregion - - #region ILogger implementation - public void Initialize (IEventSource eventSource) - { - throw new NotImplementedException (); - } - - public void Shutdown () - { - throw new NotImplementedException (); - } - - public string Parameters { - get { - throw new NotImplementedException (); - } - set { - throw new NotImplementedException (); - } - } - - public LoggerVerbosity Verbosity { - get { - throw new NotImplementedException (); - } - set { - throw new NotImplementedException (); - } - } - #endregion - } -} - diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Logging/LoggerDescription.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Logging/LoggerDescription.cs index 5115402c67..4e7b72f1a7 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.Logging/LoggerDescription.cs +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Logging/LoggerDescription.cs @@ -26,18 +26,39 @@ // using System; +using System.Reflection; using Microsoft.Build.Framework; namespace Microsoft.Build.Logging { - public class LoggerDescription - { - public LoggerDescription (string loggerClassName, string loggerAssemblyName, - string loggerAssemblyFile, string loggerSwitchParameters, - LoggerVerbosity verbosity) - { - throw new NotImplementedException (); - } - } + public class LoggerDescription + { + public LoggerDescription (string loggerClassName, string loggerAssemblyName, + string loggerAssemblyFile, string loggerSwitchParameters, + LoggerVerbosity verbosity) + { + if (loggerAssemblyName != null && loggerAssemblyFile != null) + throw new InvalidOperationException ("Cannot specify both loggerAssemblyName and loggerAssemblyFile at the same time."); + if (loggerAssemblyName == null && loggerAssemblyFile == null) + throw new InvalidOperationException ("Either loggerAssemblyName or loggerAssemblyFile must be specified"); + class_name = loggerClassName; + assembly_name = loggerAssemblyName; + assembly_file = loggerAssemblyFile; + LoggerSwitchParameters = loggerSwitchParameters; + Verbosity = verbosity; + } + + string class_name, assembly_name, assembly_file; + + public string LoggerSwitchParameters { get; private set; } + public LoggerVerbosity Verbosity { get; private set; } + + public ILogger CreateLogger () + { + var assembly = assembly_name != null ? AppDomain.CurrentDomain.Load (assembly_name) : Assembly.LoadFile (assembly_file); + var type = assembly.GetType (class_name); + return (ILogger) Activator.CreateInstance (type, Verbosity); + } + } } diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.dll.sources b/mcs/class/Microsoft.Build/Microsoft.Build.dll.sources index 37c1f480c1..6cbed8f392 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build.dll.sources +++ b/mcs/class/Microsoft.Build/Microsoft.Build.dll.sources @@ -1,6 +1,19 @@ Assembly/AssemblyInfo.cs ../../build/common/Consts.cs ../../build/common/MonoTODOAttribute.cs +../../tools/xbuild/XBuildConsts.cs +../Microsoft.Build.Engine/Microsoft.Build.BuildEngine/DirectoryScanner.cs +../Microsoft.Build.Engine/Microsoft.Build.BuildEngine/EventSource.cs +../Microsoft.Build.Engine/Microsoft.Build.BuildEngine/ColorSetter.cs +../Microsoft.Build.Engine/Microsoft.Build.BuildEngine/ColorResetter.cs +../Microsoft.Build.Engine/Microsoft.Build.BuildEngine/ConsoleLogger.cs +../Microsoft.Build.Engine/Microsoft.Build.BuildEngine/FileLogger.cs +../Microsoft.Build.Engine/Microsoft.Build.BuildEngine/WriteHandler.cs +../Microsoft.Build.Utilities/Mono.XBuild.Utilities/MSBuildUtils.cs +../Microsoft.Build.Utilities/Mono.XBuild.Utilities/ReservedNameUtils.cs +../Microsoft.Build.Utilities/Microsoft.Build.Utilities/TaskItem.cs +../Microsoft.Build.Utilities/Microsoft.Build.Utilities/ToolLocationHelper.cs +../Microsoft.Build.Utilities/Microsoft.Build.Utilities/TargetDotNetFrameworkVersion.cs Microsoft.Build.Construction/ElementLocation.cs Microsoft.Build.Construction/ProjectChooseElement.cs Microsoft.Build.Construction/ProjectCommentElement.cs @@ -28,16 +41,24 @@ Microsoft.Build.Construction/ProjectUsingTaskParameterElement.cs Microsoft.Build.Construction/ProjectWhenElement.cs Microsoft.Build.Construction/UsingTaskParameterGroupElement.cs Microsoft.Build.Evaluation/Project.cs +Microsoft.Build.Evaluation/ProjectChangedEventArgs.cs Microsoft.Build.Evaluation/ProjectCollection.cs +Microsoft.Build.Evaluation/ProjectCollectionChangedEventArgs.cs +Microsoft.Build.Evaluation/ProjectCollectionChangedState.cs Microsoft.Build.Evaluation/ProjectItem.cs Microsoft.Build.Evaluation/ProjectItemDefinition.cs Microsoft.Build.Evaluation/ProjectLoadSettings.cs Microsoft.Build.Evaluation/ProjectMetadata.cs Microsoft.Build.Evaluation/ProjectProperty.cs +Microsoft.Build.Evaluation/ProjectXmlChangedEventArgs.cs Microsoft.Build.Evaluation/ResolvedImport.cs +Microsoft.Build.Evaluation/SubToolset.cs Microsoft.Build.Evaluation/Toolset.cs Microsoft.Build.Evaluation/ToolsetDefinitionLocations.cs +Microsoft.Build.Exceptions/BuildAbortedException.cs +Microsoft.Build.Exceptions/InternalLoggerException.cs Microsoft.Build.Exceptions/InvalidProjectFileException.cs +Microsoft.Build.Exceptions/InvalidToolsetDefinitionException.cs Microsoft.Build.Execution/BuildManager.cs Microsoft.Build.Execution/BuildParameters.cs Microsoft.Build.Execution/BuildRequestData.cs @@ -45,24 +66,44 @@ Microsoft.Build.Execution/BuildRequestDataFlags.cs Microsoft.Build.Execution/BuildResult.cs Microsoft.Build.Execution/BuildResultCode.cs Microsoft.Build.Execution/BuildSubmission.cs +Microsoft.Build.Execution/BuildSubmissionCompleteCallback.cs Microsoft.Build.Execution/HostServices.cs Microsoft.Build.Execution/ITargetResult.cs Microsoft.Build.Execution/NodeAffinity.cs +Microsoft.Build.Execution/NodeEngineShutdownReason.cs +Microsoft.Build.Execution/OutOfProcNode.cs Microsoft.Build.Execution/ProjectInstance.cs Microsoft.Build.Execution/ProjectItemDefinitionInstance.cs +Microsoft.Build.Execution/ProjectItemGroupTaskInstance.cs +Microsoft.Build.Execution/ProjectItemGroupTaskItemInstance.cs +Microsoft.Build.Execution/ProjectItemGroupTaskMetadataInstance.cs Microsoft.Build.Execution/ProjectItemInstance.cs Microsoft.Build.Execution/ProjectMetadataInstance.cs +Microsoft.Build.Execution/ProjectOnErrorInstance.cs +Microsoft.Build.Execution/ProjectPropertyGroupTaskInstance.cs +Microsoft.Build.Execution/ProjectPropertyGroupTaskPropertyInstance.cs Microsoft.Build.Execution/ProjectPropertyInstance.cs Microsoft.Build.Execution/ProjectTargetInstance.cs +Microsoft.Build.Execution/ProjectTaskInstance.cs +Microsoft.Build.Execution/ProjectTaskInstanceChild.cs +Microsoft.Build.Execution/ProjectTaskOutputItemInstance.cs +Microsoft.Build.Execution/ProjectTaskOutputPropertyInstance.cs +Microsoft.Build.Execution/ProjectTargetInstanceChild.cs Microsoft.Build.Execution/TargetResult.cs Microsoft.Build.Execution/TargetResultCode.cs +Microsoft.Build.Internal/BuildEngine4.cs +Microsoft.Build.Internal/BuildNodeManager.cs +Microsoft.Build.Internal/BuildTaskDatabase.cs +Microsoft.Build.Internal/BuildTaskFactory.cs Microsoft.Build.Internal/CollectionFromEnumerable.cs +Microsoft.Build.Internal/ExpressionConstructs.cs +Microsoft.Build.Internal/ExpressionEvaluator.cs +Microsoft.Build.Internal/ExpressionParserManual.cs +Microsoft.Build.Internal/ExpressionTokenizer.cs Microsoft.Build.Internal/FilteredEnumerable.cs +Microsoft.Build.Internal/ProjectTaskItem.cs Microsoft.Build.Internal/ReverseEnumerable.cs -Microsoft.Build.Logging/ColorResetter.cs -Microsoft.Build.Logging/ColorSetter.cs -Microsoft.Build.Logging/ConsoleLogger.cs -Microsoft.Build.Logging/FileLogger.cs +Microsoft.Build.Internal/WindowsCompatibilityExtensions.cs +Microsoft.Build.Logging/ConfigurableForwardingLogger.cs Microsoft.Build.Logging/ForwardingLoggerRecord.cs Microsoft.Build.Logging/LoggerDescription.cs -Microsoft.Build.Logging/WriteHandler.cs diff --git a/mcs/class/Microsoft.Build/Microsoft.Build_test.dll.sources b/mcs/class/Microsoft.Build/Microsoft.Build_test.dll.sources index 0258038dcc..86971873d0 100644 --- a/mcs/class/Microsoft.Build/Microsoft.Build_test.dll.sources +++ b/mcs/class/Microsoft.Build/Microsoft.Build_test.dll.sources @@ -1,2 +1,21 @@ FunctionalTest.cs +Microsoft.Build.Construction/ProjectItemElementTest.cs +Microsoft.Build.Construction/ProjectRootElementTest.cs +Microsoft.Build.Evaluation/ProjectCollectionTest.cs +Microsoft.Build.Evaluation/ProjectItemDefinitionTest.cs +Microsoft.Build.Evaluation/ProjectItemTest.cs +Microsoft.Build.Evaluation/ProjectTest.cs +Microsoft.Build.Evaluation/ProjectPropertyTest.cs +Microsoft.Build.Evaluation/ResolvedImportTest.cs +Microsoft.Build.Evaluation/ToolsetTest.cs +Microsoft.Build.Execution/BuildParametersTest.cs +Microsoft.Build.Execution/BuildManagerTest.cs +Microsoft.Build.Execution/BuildSubmissionTest.cs +Microsoft.Build.Execution/ProjectInstanceTest.cs +Microsoft.Build.Execution/ProjectMetadataInstanceTest.cs +Microsoft.Build.Execution/ProjectTargetInstanceTest.cs Microsoft.Build.Internal/CollectionFromEnumerableTest.cs +Microsoft.Build.Internal/ExpressionParserTest.cs +Microsoft.Build.Logging/ConsoleLoggerTest.cs +Microsoft.Build.Logging/LoggerDescriptionTest.cs + diff --git a/mcs/class/Microsoft.Build/Test/Microsoft.Build.Construction/ProjectItemElementTest.cs b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Construction/ProjectItemElementTest.cs new file mode 100644 index 0000000000..caa93f1b76 --- /dev/null +++ b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Construction/ProjectItemElementTest.cs @@ -0,0 +1,42 @@ +using System; +using System.IO; +using System.Linq; +using System.Xml; +using Microsoft.Build.Construction; +using NUnit.Framework; +using Microsoft.Build.Evaluation; +using Microsoft.Build.Exceptions; + +namespace MonoTests.Microsoft.Build.Construction +{ + [TestFixture] + public class ProjectItemElementTest + { + [Test] + [ExpectedException (typeof (InvalidProjectFileException))] + public void EmptyInclude () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <ItemGroup> + <Foo Include='' /> + </ItemGroup> +</Project>"; + var xml = XmlReader.Create (new StringReader (project_xml)); + ProjectRootElement.Create (xml); + } + + [Test] + [ExpectedException (typeof (InvalidProjectFileException))] + public void MissingInclude () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <ItemGroup> + <Foo /> + </ItemGroup> +</Project>"; + var xml = XmlReader.Create (new StringReader (project_xml)); + ProjectRootElement.Create (xml); + } + } +} + diff --git a/mcs/class/Microsoft.Build/Test/Microsoft.Build.Construction/ProjectRootElementTest.cs b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Construction/ProjectRootElementTest.cs new file mode 100644 index 0000000000..e864f4dfb3 --- /dev/null +++ b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Construction/ProjectRootElementTest.cs @@ -0,0 +1,222 @@ +using System; +using System.IO; +using System.Linq; +using System.Xml; +using Microsoft.Build.Construction; +using NUnit.Framework; +using Microsoft.Build.Evaluation; +using Microsoft.Build.Exceptions; + +namespace MonoTests.Microsoft.Build.Construction +{ + [TestFixture] + public class ProjectRootElementTest + { + const string empty_project_xml = "<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' />"; + + [Test] + [ExpectedException (typeof (UriFormatException))] + [Category ("NotWorking")] // URL is constructed for ElementLocation, which we don't support yet. + public void CreateExpectsAbsoluteUri () + { + var xml = XmlReader.Create (new StringReader (empty_project_xml), null, "foo.xml"); + ProjectRootElement.Create (xml); + } + + [Test] + public void CreateAndPaths () + { + Assert.IsNull (ProjectRootElement.Create ().FullPath, "#1"); + var xml = XmlReader.Create (new StringReader (empty_project_xml), null, "file:///foo.xml"); + // This creator does not fill FullPath... + var root = ProjectRootElement.Create (xml); + Assert.IsNull (root.FullPath, "#2"); + Assert.AreEqual (Path.GetDirectoryName (new Uri (GetType ().Assembly.CodeBase).LocalPath), root.DirectoryPath, "#3"); + } + + [Test] + public void FullPathSetter () + { + var root = ProjectRootElement.Create (); + root.FullPath = "test" + Path.DirectorySeparatorChar + "foo.xml"; + var full = Path.Combine (Path.GetDirectoryName (new Uri (GetType ().Assembly.CodeBase).LocalPath), "test", "foo.xml"); + Assert.AreEqual (full, root.FullPath, "#1"); + Assert.AreEqual (Path.GetDirectoryName (full), root.DirectoryPath, "#1"); + } + + [Test] + [ExpectedException (typeof (ArgumentNullException))] + public void FullPathSetNull () + { + ProjectRootElement.Create ().FullPath = null; + } + + [Test] + public void InvalidProject () + { + try { + ProjectRootElement.Create (XmlReader.Create (new StringReader (" <root/>"))); + Assert.Fail ("should throw InvalidProjectFileException"); + } catch (InvalidProjectFileException ex) { + #if NET_4_5 + Assert.AreEqual (1, ex.LineNumber, "#1"); + // it is very interesting, but unlike XmlReader.LinePosition it returns the position for '<'. + Assert.AreEqual (2, ex.ColumnNumber, "#2"); + #endif + } + } + + [Test] + public void CreateWithXmlLoads () + { + string project_xml_1 = "<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'><ItemGroup><None Include='bar.txt' /></ItemGroup></Project>"; + var xml = XmlReader.Create (new StringReader (project_xml_1), null, "file://localhost/foo.xml"); + var root = ProjectRootElement.Create (xml); + Assert.AreEqual (1, root.Items.Count, "#1"); + } + + [Test] + public void ToolsVersionDefault () + { + var g = ProjectCollection.GlobalProjectCollection; + var root = ProjectRootElement.Create (); + // this will be wrong in the future version, but since .NET 4.5 still expects "4.0" we can't say for sure. + Assert.AreEqual ("4.0", root.ToolsVersion, "#1"); + } + + [Test] + public void ToolsVersionIsEmptyWithXml () + { + string project_xml_1 = "<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'><ItemGroup><None Include='bar.txt' /></ItemGroup></Project>"; + var xml = XmlReader.Create (new StringReader (project_xml_1), null, "file://localhost/foo.xml"); + var root = ProjectRootElement.Create (xml); + Assert.AreEqual (string.Empty, root.ToolsVersion, "#1"); + } + + [Test] + public void LoadUnknownChild () + { + string project_xml_1 = "<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'><Unknown /></Project>"; + var xml = XmlReader.Create (new StringReader (project_xml_1), null, "file://localhost/foo.xml"); + try { + ProjectRootElement.Create (xml); + Assert.Fail ("should throw InvalidProjectFileException"); + } catch (InvalidProjectFileException ex) { + #if NET_4_5 + Assert.AreEqual (1, ex.LineNumber, "#1"); + // unlike unexpected element case which returned the position for '<', it does return the name start char... + Assert.AreEqual (70, ex.ColumnNumber, "#2"); + #endif + } + } + + [Test] + public void LoadUnregisteredItem () + { + string project_xml_1 = "<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'><ItemGroup><UnregisteredItem Include='bar.txt' /></ItemGroup></Project>"; + var xml = XmlReader.Create (new StringReader (project_xml_1), null, "file://localhost/foo.xml"); + var root = ProjectRootElement.Create (xml); + Assert.AreEqual (1, root.Items.Count, "#1"); + } + + [Test] + public void LoadInvalidProjectForBadCondition () + { + string xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <PropertyGroup> + <Foo>What are 'ESCAPE' & ""EVALUATE"" ? $ # % ^</Foo> + <!-- Note that this contains invalid Condition expression. Project.ctor() fails to load. --> + <Baz Condition=""$(Void)=="">$(FOO)</Baz> + </PropertyGroup> +</Project>"; + var path = "file://localhost/foo.xml"; + var reader = XmlReader.Create (new StringReader (xml), null, path); + var root = ProjectRootElement.Create (reader); + Assert.AreEqual (2, root.Properties.Count, "#1"); + } + + [Test] + [ExpectedException (typeof (InvalidProjectFileException))] + public void LoadInvalidProjectGroupInProjectGroup () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <Import Project='$(MSBuildToolsPath)\Microsoft.CSharp.targets' /> + <PropertyGroup> + <Foo>Bar</Foo> + <PropertyGroup> + <X>x</X> + <Y>y</Y> + <Z>z</Z> + </PropertyGroup> + </PropertyGroup> +</Project>"; + var xml = XmlReader.Create (new StringReader (project_xml)); + ProjectRootElement.Create (xml); + } + + [Test] + [ExpectedException (typeof (InvalidProjectFileException))] + public void LoadInvalidItemGroupInProjectGroup () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <Import Project='$(MSBuildToolsPath)\Microsoft.CSharp.targets' /> + <PropertyGroup> + <Foo>Bar</Foo> + <ItemGroup/> + </PropertyGroup> +</Project>"; + var xml = XmlReader.Create (new StringReader (project_xml)); + ProjectRootElement.Create (xml); + } + + [Test] + public void ChildAndAllChildren () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <Import Project='$(MSBuildToolsPath)\Microsoft.CSharp.targets' /> + <PropertyGroup> + <Foo>Bar</Foo> + <Item/> + </PropertyGroup> +</Project>"; + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + Assert.AreEqual (2, root.Children.Count, "#1"); + // AllChildren expands descendants + Assert.AreEqual (4, root.AllChildren.Count (), "#2"); + } + + [Test] + [ExpectedException (typeof (InvalidOperationException))] + public void SaveWithoutFullPath () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' />"; + var xml = XmlReader.Create (new StringReader (project_xml), null, "file://localhost/foo.xml"); + var root = ProjectRootElement.Create (xml); + root.Save (); + } + + [Test] + public void SaveToWriter () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' />"; + var xml = XmlReader.Create (new StringReader (project_xml), null, "file://localhost/foo.xml"); + var root = ProjectRootElement.Create (xml); + var sw = new StringWriter (); + root.Save (sw); + // CRLF? mmm, k... + Assert.AreEqual ("<?xml version=\"1.0\" encoding=\"utf-16\"?>\r\n" + project_xml.Replace ('\'', '"'), sw.ToString (), "#1"); + } + + [Test] + [ExpectedException (typeof (InvalidProjectFileException))] + public void ImportsMissingProject () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <Import Project='' /> +</Project>"; + var xml = XmlReader.Create (new StringReader (project_xml)); + ProjectRootElement.Create (xml); + } + } +} diff --git a/mcs/class/Microsoft.Build/Test/Microsoft.Build.Evaluation/ProjectCollectionTest.cs b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Evaluation/ProjectCollectionTest.cs new file mode 100644 index 0000000000..700a41663d --- /dev/null +++ b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Evaluation/ProjectCollectionTest.cs @@ -0,0 +1,150 @@ +// +// ProjectCollectionTest.cs +// +// Author: +// Atsushi Enomoto (atsushi@xamarin.com) +// +// Copyright (C) 2013 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.IO; +using System.Linq; +using System.Xml; +using Microsoft.Build.Construction; +using Microsoft.Build.Evaluation; +using NUnit.Framework; +using Microsoft.Build.Execution; + +namespace MonoTests.Microsoft.Build.Evaluation +{ + [TestFixture] + public class ProjectCollectionTest + { + [Test] + public void GlobalProperties () + { + var g = ProjectCollection.GlobalProjectCollection; + Assert.AreEqual (0, g.GlobalProperties.Count, "#1"); + Assert.IsTrue (g.GlobalProperties.IsReadOnly, "#2"); + } + + [Test] + public void DefaultToolsVersion () + { + var pc = ProjectCollection.GlobalProjectCollection; + Assert.AreEqual (pc.Toolsets.First ().ToolsVersion, pc.DefaultToolsVersion, "#1"); + } + + [Test] + public void Toolsets () + { + var pc = ProjectCollection.GlobalProjectCollection; + Assert.IsNotNull (pc.Toolsets, "#1-1"); + Assert.IsTrue (pc.Toolsets.Any (), "#1-2"); + pc = new ProjectCollection (); + Assert.IsNotNull (pc.Toolsets, "#2-1"); + Assert.IsTrue (pc.Toolsets.Any (), "#2-2"); + } + + [Test] + public void BuildDoesNotIncreaseCollectionContent () + { + string empty_project_xml = "<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' />"; + var xml = XmlReader.Create (new StringReader (empty_project_xml)); + var root = ProjectRootElement.Create (xml); + var coll = new ProjectCollection (); + var inst = new ProjectInstance (root, null, null, coll); + root.FullPath = "ProjectCollectionTest.BuildDoesNotIncreaseCollectionContent.proj"; + Assert.AreEqual (0, coll.Count, "#1"); + inst.Build (); + Assert.AreEqual (0, coll.Count, "#2"); + } + + [Test] + public void GetLoadedProjectsWithoutFullPath () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' />"; + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + string path = Path.GetFullPath ("foo.xml"); + var pc = new ProjectCollection (); + + pc.LoadProject (XmlReader.Create (new StringReader (project_xml), null, path)); + Assert.AreEqual (0, pc.GetLoadedProjects (path).Count, "#1"); // huh? + Assert.AreEqual (0, pc.LoadedProjects.Count, "#1.1"); + + new Project (root, null, null, pc); + Assert.AreEqual (0, pc.GetLoadedProjects (path).Count, "#2"); // huh? + } + + [Test] + public void GetLoadedProjectsSuccess () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' />"; + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + string path = Path.GetFullPath ("foo.xml"); + var pc = new ProjectCollection (); + + var proj = new Project (root, null, null, pc); + // this order also matters for test; It sets FullPath after Project.ctor(), and should still work. + root.FullPath = "foo.xml"; + + Assert.AreEqual (1, pc.GetLoadedProjects (path).Count, "#1"); // wow ok... + Assert.AreEqual (proj, pc.GetLoadedProjects (path).First (), "#2"); + } + + [Test] + public void GetLoadedProjectsSuccess2 () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' />"; + string path = Path.GetFullPath ("GetLoadedProjectsSuccess2.xml"); + var pc = new ProjectCollection (); + + using (var fs = File.CreateText (path)) + fs.Write (project_xml); + try { + var proj = pc.LoadProject (path); + + Assert.AreEqual (1, pc.GetLoadedProjects (path).Count, "#1"); // ok... LoadProject (with filename) adds it to the collection. + Assert.AreEqual (proj, pc.GetLoadedProjects (path).First (), "#2"); + } finally { + File.Delete (path); + } + } + + [Test] + public void GetLoadedProjectsForProjectInstance () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' />"; + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + string path = Path.GetFullPath ("foo.xml"); + var pc = new ProjectCollection (); + root.FullPath = "foo.xml"; + + new ProjectInstance (root, null, null, pc); + Assert.AreEqual (0, pc.GetLoadedProjects (path).Count, "#1"); // so, ProjectInstance does not actually load Project... + } + } +} + diff --git a/mcs/class/Microsoft.Build/Test/Microsoft.Build.Evaluation/ProjectItemDefinitionTest.cs b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Evaluation/ProjectItemDefinitionTest.cs new file mode 100644 index 0000000000..cd86873c84 --- /dev/null +++ b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Evaluation/ProjectItemDefinitionTest.cs @@ -0,0 +1,104 @@ +// +// ProjectItemDefinitionTest.cs +// +// Author: +// Atsushi Enomoto (atsushi@xamarin.com) +// +// Copyright (C) 2013 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.IO; +using System.Linq; +using System.Xml; +using Microsoft.Build.Construction; +using Microsoft.Build.Evaluation; +using Microsoft.Build.Execution; +using NUnit.Framework; +using System.Collections.Generic; + +namespace MonoTests.Microsoft.Build.Evaluation +{ + [TestFixture] + public class ProjectItemDefinitionTest + { + [Test] + public void Properties () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <ItemDefinitionGroup> + <Foo> + <prop1>value1</prop1> + <prop2>value1</prop2> + </Foo> + <!-- This one is merged into existing Foo definition above. --> + <Foo> + <prop1>valueX1</prop1><!-- this one overrides value1. --> + <prop3>value3</prop3> + </Foo> + </ItemDefinitionGroup> +</Project>"; + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + var proj = new Project (root); + Assert.AreEqual (1, proj.ItemDefinitions.Count, "#1"); // Foo + var def = proj.ItemDefinitions ["Foo"]; + Assert.AreEqual ("Foo", def.ItemType, "#1x"); + Assert.AreEqual (3, def.MetadataCount, "#2"); + var md1 = def.Metadata.First (m => m.Name == "prop1"); + Assert.AreEqual ("Foo", md1.ItemType, "#2x"); + Assert.AreEqual ("valueX1", md1.UnevaluatedValue, "#3"); + // FIXME: enable it once we implemented it. + //Assert.AreEqual ("valueX1", md1.EvaluatedValue, "#4"); + Assert.IsNotNull (md1.Predecessor, "#5"); + Assert.AreEqual ("value1", md1.Predecessor.UnevaluatedValue, "#6"); + // FIXME: enable it once we implemented it. + //Assert.AreEqual ("value1", md1.Predecessor.EvaluatedValue, "#7"); + } + + [Test] + public void Condition () + { + string xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <ItemDefinitionGroup> + <I Condition='{0}'> + <DefinedMetadata>X</DefinedMetadata> + </I> + </ItemDefinitionGroup> + <ItemGroup> + <I Include='foo' /> + </ItemGroup> +</Project>"; + var reader = XmlReader.Create (new StringReader (string.Format (xml, "True"))); + var root = ProjectRootElement.Create (reader); + var proj = new Project (root); + var i = proj.GetItems ("I").First (); + Assert.AreEqual ("X", i.GetMetadataValue ("DefinedMetadata"), "#1"); + + reader = XmlReader.Create (new StringReader (string.Format (xml, "False"))); + root = ProjectRootElement.Create (reader); + proj = new Project (root); + i = proj.GetItems ("I").First (); + Assert.AreEqual (string.Empty, i.GetMetadataValue ("DefinedMetadata"), "#2"); + } + } +} + diff --git a/mcs/class/Microsoft.Build/Test/Microsoft.Build.Evaluation/ProjectItemTest.cs b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Evaluation/ProjectItemTest.cs new file mode 100644 index 0000000000..61973b7ceb --- /dev/null +++ b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Evaluation/ProjectItemTest.cs @@ -0,0 +1,212 @@ +// +// ProjectItemTest.cs +// +// Author: +// Atsushi Enomoto (atsushi@xamarin.com) +// +// Copyright (C) 2013 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.IO; +using System.Linq; +using System.Xml; +using Microsoft.Build.Construction; +using Microsoft.Build.Evaluation; +using Microsoft.Build.Execution; +using NUnit.Framework; +using System.Collections.Generic; + +namespace MonoTests.Microsoft.Build.Evaluation +{ + [TestFixture] + public class ProjectItemTest + { + [Test] + public void SetUnevaluatedInclude () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <ItemGroup> + <Foo Include='foo/bar.txt' /> + </ItemGroup> +</Project>"; + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + Assert.AreEqual (1, root.ItemGroups.Count, "#1"); + var g = root.ItemGroups.First (); + Assert.AreEqual (1, g.Items.Count, "#2"); + var xitem = g.Items.First (); + var proj = new Project (root); + var item = proj.ItemsIgnoringCondition.First (); + string inc = "foo/bar.txt"; + Assert.AreEqual (inc, xitem.Include, "#3"); + Assert.AreEqual (inc, item.UnevaluatedInclude, "#4"); + string inc2 = "foo/bar.ext.txt"; + item.UnevaluatedInclude = inc2; + Assert.AreEqual (inc2, xitem.Include, "#5"); + Assert.AreEqual (inc2, item.UnevaluatedInclude, "#6"); + } + + void SetupTemporaryDirectoriesAndFiles () + { + Directory.CreateDirectory ("Test/ProjectItemTestTemporary"); + Directory.CreateDirectory ("Test/ProjectItemTestTemporary/parent"); + Directory.CreateDirectory ("Test/ProjectItemTestTemporary/parent/dir1"); + Directory.CreateDirectory ("Test/ProjectItemTestTemporary/parent/dir2"); + File.CreateText ("Test/ProjectItemTestTemporary/x.cs").Close (); + File.CreateText ("Test/ProjectItemTestTemporary/parent/dir1/a.cs").Close (); + File.CreateText ("Test/ProjectItemTestTemporary/parent/dir1/a1.cs").Close (); + File.CreateText ("Test/ProjectItemTestTemporary/parent/dir1/b.cs").Close (); + File.CreateText ("Test/ProjectItemTestTemporary/parent/dir2/a2.cs").Close (); + File.CreateText ("Test/ProjectItemTestTemporary/parent/dir2/a.cs").Close (); + File.CreateText ("Test/ProjectItemTestTemporary/parent/dir2/b.cs").Close (); + } + + void CleanupTemporaryDirectories () + { + Directory.Delete ("Test/ProjectItemTestTemporary", true); + } + + [Test] + public void WildcardExpansion () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <ItemGroup> + <Foo Include='Test/ProjectItemTestTemporary/parent/dir*/a*.cs;Test/ProjectItemTestTemporary/x.cs' /> + </ItemGroup> +</Project>"; + try { + SetupTemporaryDirectoriesAndFiles (); + WildcardExpansionCommon (project_xml, false); + } finally { + CleanupTemporaryDirectories (); + } + } + + [Test] + public void WildcardExpansionRecursive () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <ItemGroup> + <Foo Include='Test/ProjectItemTestTemporary/parent/**/a*.cs;Test/ProjectItemTestTemporary/x.cs' /> + </ItemGroup> +</Project>"; + try { + SetupTemporaryDirectoriesAndFiles (); + WildcardExpansionCommon (project_xml, true); + } finally { + CleanupTemporaryDirectories (); + } + } + + void WildcardExpansionCommon (string xmlString, bool hasRecursiveDir) + { + char sep = Path.DirectorySeparatorChar; + var xml = XmlReader.Create (new StringReader (xmlString)); + var root = ProjectRootElement.Create (xml); + var proj = new Project (root); + var xitem = proj.Xml.Items.First (); + // sort is needed because they are only sorted by ItemType. + var items = proj.Items.OrderBy (p => p.EvaluatedInclude).ToArray (); + Assert.AreEqual (5, items.Length, "#1"); + Assert.AreEqual (string.Format ("Test/ProjectItemTestTemporary/parent/dir1{0}a.cs", Path.DirectorySeparatorChar), items [0].EvaluatedInclude, "#2"); + Assert.AreEqual ("a", items [0].GetMetadataValue ("Filename"), "#3"); + if (hasRecursiveDir) + Assert.AreEqual ("dir1" + sep, items [0].GetMetadataValue ("RecursiveDir"), "#3.2"); + Assert.AreEqual (string.Format ("Test/ProjectItemTestTemporary/parent/dir1{0}a1.cs", Path.DirectorySeparatorChar), items [1].EvaluatedInclude, "#4"); + Assert.AreEqual ("a1", items [1].GetMetadataValue ("Filename"), "#5"); + if (hasRecursiveDir) + Assert.AreEqual ("dir1" + sep, items [1].GetMetadataValue ("RecursiveDir"), "#5.2"); + Assert.AreEqual (string.Format ("Test/ProjectItemTestTemporary/parent/dir2{0}a.cs", Path.DirectorySeparatorChar), items [2].EvaluatedInclude, "#6"); + Assert.AreEqual ("a", items [2].GetMetadataValue ("Filename"), "#7"); + if (hasRecursiveDir) + Assert.AreEqual ("dir2" + sep, items [2].GetMetadataValue ("RecursiveDir"), "#7.2"); + Assert.AreEqual (string.Format ("Test/ProjectItemTestTemporary/parent/dir2{0}a2.cs", Path.DirectorySeparatorChar), items [3].EvaluatedInclude, "#8"); + Assert.AreEqual ("a2", items [3].GetMetadataValue ("Filename"), "#9"); + if (hasRecursiveDir) + Assert.AreEqual ("dir2" + sep, items [3].GetMetadataValue ("RecursiveDir"), "#9.2"); + Assert.AreEqual ("Test/ProjectItemTestTemporary/x.cs", items [4].EvaluatedInclude, "#10"); + for (int i = 0; i < items.Length; i++) + Assert.AreEqual (xitem, items [i].Xml, "#11:" + i); + } + + [Test] + public void Metadata () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <ItemDefinitionGroup> + <Foo> + <prop1>value1</prop1> + </Foo> + </ItemDefinitionGroup> + <ItemGroup> + <Foo Include='foo/bar.txt'> + <prop1>valueX1</prop1> + </Foo> + </ItemGroup> +</Project>"; + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + Assert.AreEqual (1, root.ItemGroups.Count, "#1"); + var g = root.ItemGroups.First (); + Assert.AreEqual (1, g.Items.Count, "#2"); + var proj = new Project (root); + var item = proj.ItemsIgnoringCondition.First (); + var meta = item.GetMetadata ("prop1"); + Assert.IsNotNull (meta, "#3"); + Assert.AreEqual ("valueX1", meta.UnevaluatedValue, "#4"); + Assert.IsNotNull (meta.Predecessor, "#5"); + Assert.AreEqual ("value1", meta.Predecessor.UnevaluatedValue, "#6"); + + // Well-known metadata don't show up via GetMetadata(), but does show up via GetMetadataValue(). + Assert.AreEqual (null, item.GetMetadata ("Filename"), "#7"); + Assert.AreEqual ("bar", item.GetMetadataValue ("Filename"), "#8"); + } + + [Test] + public void ExpandPropertyThenTrim () + { + string test = @"A +B +C + "; + string project_xml = string.Format (@"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <PropertyGroup> + <Test>{0}</Test> + <Test2>$(TEST)</Test2> + </PropertyGroup> + <ItemGroup> + <X Include='$(TEST)' /> + <X2 Include='$(TEST)z' /> + </ItemGroup> +</Project>", test); + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + root.FullPath = "ProjectItemTest.ExpandPropertyThenTrim.proj"; + var proj = new ProjectInstance (root); + Assert.AreEqual (test, proj.GetPropertyValue ("TEST"), "#1"); + Assert.AreEqual (test, proj.GetPropertyValue ("TEST2"), "#2"); + Assert.AreEqual (test.Trim (), proj.GetItems ("X").First ().EvaluatedInclude, "#3"); + Assert.AreEqual (test + "z", proj.GetItems ("X2").First ().EvaluatedInclude, "#4"); + } + } +} + diff --git a/mcs/class/Microsoft.Build/Test/Microsoft.Build.Evaluation/ProjectPropertyTest.cs b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Evaluation/ProjectPropertyTest.cs new file mode 100644 index 0000000000..9c89ef09f6 --- /dev/null +++ b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Evaluation/ProjectPropertyTest.cs @@ -0,0 +1,130 @@ +// +// ProjectPropertyTest.cs +// +// Author: +// Atsushi Enomoto (atsushi@xamarin.com) +// +// Copyright (C) 2013 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.IO; +using System.Linq; +using System.Xml; +using Microsoft.Build.Construction; +using Microsoft.Build.Evaluation; +using Microsoft.Build.Execution; +using NUnit.Framework; +using System.Collections.Generic; + +namespace MonoTests.Microsoft.Build.Evaluation +{ + [TestFixture] + public class ProjectPropertyTest + { + [Test] + public void SetUnevaluatedValueOverwritesElementValue () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <PropertyGroup> + <Foo>Bar</Foo> + <Item/> + <X>1</X> + <X>2</X> + <PATH>overriden</PATH> + </PropertyGroup> +</Project>"; + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + var pe = root.Properties.First (); + Assert.AreEqual ("Bar", pe.Value, "#1"); + var proj = new Project (root); + var prop = proj.Properties.First (p => p.Name == "Foo"); + Assert.AreEqual ("Bar", prop.UnevaluatedValue, "#2"); + prop.UnevaluatedValue = "x"; + Assert.AreEqual ("x", pe.Value, "#3"); + + prop = proj.Properties.First (p => p.Name == "X"); + Assert.AreEqual ("2", prop.UnevaluatedValue, "#4"); + Assert.IsNotNull (prop.Predecessor, "#5"); + Assert.AreEqual ("1", prop.Predecessor.UnevaluatedValue, "#6"); + + // environment property could also be Predecessor (and removed...maybe. + // I could reproduce only NRE = .NET bug with environment property so far.) + prop = proj.Properties.First (p => p.Name == "PATH"); + Assert.AreEqual ("overriden", prop.UnevaluatedValue, "#7"); + Assert.IsNotNull (prop.Predecessor, "#8"); + } + + [Test] + [ExpectedException (typeof(InvalidOperationException))] + public void UpdateGlobalPropertyValue () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' />"; + var xml = XmlReader.Create (new StringReader (project_xml)); + var props = new Dictionary<string, string> (); + props.Add ("GP", "GV"); + var root = ProjectRootElement.Create (xml); + var proj = new Project (root, props, null); + var pe = proj.Properties.First (p => p.IsGlobalProperty); + pe.UnevaluatedValue = "UPDATED"; + } + + [Test] + [ExpectedException (typeof (InvalidOperationException))] + public void UpdateEnvironmentPropertyValue () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' />"; + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + var proj = new Project (root); + var pe = proj.Properties.First (p => p.IsEnvironmentProperty); + pe.UnevaluatedValue = "UPDATED"; + } + + [Test] + public void DeepReferences () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <PropertyGroup> + <A>1</A> + <B>$(A)+1</B> + <C>$(B)+2</C> + </PropertyGroup> +</Project>"; + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + Assert.AreEqual ("1+1+2", new Project (root).GetProperty ("C").EvaluatedValue, "#1"); + Assert.AreEqual ("1+1+2", new ProjectInstance (root).GetProperty ("C").EvaluatedValue, "#1"); + } + + [Test] + public void PlatformPropertyEmptyByDefault () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' />"; + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + var proj = new Project (root); + Assert.IsNull (proj.GetProperty ("PLATFORM"), "#1"); + } + } +} + diff --git a/mcs/class/Microsoft.Build/Test/Microsoft.Build.Evaluation/ProjectTest.cs b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Evaluation/ProjectTest.cs new file mode 100644 index 0000000000..4ea4551816 --- /dev/null +++ b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Evaluation/ProjectTest.cs @@ -0,0 +1,253 @@ +// +// ProjectTest.cs +// +// Author: +// Atsushi Enomoto (atsushi@xamarin.com) +// +// Copyright (C) 2013 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.IO; +using System.Linq; +using System.Xml; +using Microsoft.Build.Construction; +using Microsoft.Build.Evaluation; +using NUnit.Framework; +using Microsoft.Build.Exceptions; +using Microsoft.Build.Logging; +using Microsoft.Build.Framework; + +namespace MonoTests.Microsoft.Build.Evaluation +{ + [TestFixture] + public class ProjectTest + { + [Test] + public void EscapeDoesWTF () + { + string value_xml = "What are 'ESCAPE' & \"EVALUATE\" ? $ # % ^"; + string value = "What are 'ESCAPE' & \"EVALUATE\" ? $ # % ^"; + string escaped = "What are %27ESCAPE%27 & \"EVALUATE\" %3f %24 # %25 ^"; + string xml = string.Format (@"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <PropertyGroup> + <Foo>{0}</Foo> + <Baz>$(FOO)</Baz> + </PropertyGroup> +</Project>", value_xml); + var path = "file://localhost/foo.xml"; + var reader = XmlReader.Create (new StringReader (xml), null, path); + var root = ProjectRootElement.Create (reader); + var proj = new Project (root); + var prop = proj.Properties.First (p => p.Name == "Foo"); + Assert.AreEqual (value, prop.UnevaluatedValue, "#1"); + Assert.AreEqual (value, prop.EvaluatedValue, "#2"); + // eh? + Assert.AreEqual (value, Project.GetPropertyValueEscaped (prop), "#3"); + prop = proj.Properties.First (p => p.Name == "Baz"); + Assert.AreEqual ("$(FOO)", prop.UnevaluatedValue, "#4"); + Assert.AreEqual (value, prop.EvaluatedValue, "#5"); + // eh? + Assert.AreEqual (value, Project.GetPropertyValueEscaped (prop), "#6"); + + // OK you are fine. + Assert.AreEqual (escaped, ProjectCollection.Escape (value), "#7"); + } + + [Test] + public void FullPath () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' />"; + var xml = XmlReader.Create (new StringReader (project_xml), null, "file://localhost/foo.xml"); + var root = ProjectRootElement.Create (xml); + var proj = new Project (root); + proj.FullPath = "ABC"; + Assert.IsTrue (proj.FullPath.EndsWith (Path.DirectorySeparatorChar + "ABC"), "#1"); + Assert.AreEqual (root.FullPath, proj.FullPath, "#2"); + } + + [Test] + public void BuildEmptyProject () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' />"; + var xml = XmlReader.Create (new StringReader (project_xml), null, "file://localhost/foo.xml"); + var root = ProjectRootElement.Create (xml); + root.FullPath = "ProjectTest.BuildEmptyProject.proj"; + + // This seems to do nothing and still returns true + Assert.IsTrue (new Project (root) { FullPath = "ProjectTest.BuildEmptyProject.1.proj" }.Build (), "#1"); + // This seems to fail to find the appropriate target + Assert.IsFalse (new Project (root) { FullPath = "ProjectTest.BuildEmptyProject.2.proj" }.Build ("Build", null), "#2"); + // Thus, this tries to build all the targets (empty) and no one failed, so returns true(!) + Assert.IsTrue (new Project (root) { FullPath = "ProjectTest.BuildEmptyProject.3.proj" }.Build (new string [0], null), "#3"); + // Actially null "targets" is accepted and returns true(!!) + Assert.IsTrue (new Project (root) { FullPath = "ProjectTest.BuildEmptyProject.4.proj" }.Build ((string []) null, null), "#4"); + // matching seems to be blindly done, null string also results in true(!!) + Assert.IsTrue (new Project (root) { FullPath = "ProjectTest.BuildEmptyProject.5.proj" }.Build ((string) null, null), "#5"); + } + + [Test] + [ExpectedException (typeof (InvalidProjectFileException))] + public void LoadInvalidProjectForBadCondition () + { + string xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <PropertyGroup> + <Foo>What are 'ESCAPE' & ""EVALUATE"" ? $ # % ^</Foo> + <!-- Note that this contains invalid Condition expression, yet ProjectElement.Create() does NOT fail. --> + <Baz Condition=""$(Void)=="">$(FOO)</Baz> + </PropertyGroup> +</Project>"; + var reader = XmlReader.Create (new StringReader (xml)); + var root = ProjectRootElement.Create (reader); + new Project (root); + } + + [Test] + public void ExpandString () + { + string xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <PropertyGroup> + <Foo>What are 'ESCAPE' & ""EVALUATE"" ? $ # % ^</Foo> + <Bar>y</Bar> + <Baz Condition=""$(Void)==''"">$(FOO)</Baz> + </PropertyGroup> +</Project>"; + var reader = XmlReader.Create (new StringReader (xml)); + var root = ProjectRootElement.Create (reader); + var proj = new Project (root); + root.FullPath = "ProjectTest.ExpandString.proj"; + Assert.AreEqual ("xyz", proj.ExpandString ("x$(BAR)z"), "#1"); + Assert.AreEqual ("x$(BARz", proj.ExpandString ("x$(BARz"), "#2"); // incomplete + Assert.AreEqual ("xz", proj.ExpandString ("x@(BAR)z"), "#3"); // not an item + } + + [Test] + public void BuildCSharpTargetGetFrameworkPaths () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <Import Project='$(MSBuildToolsPath)\Microsoft.CSharp.targets' /> +</Project>"; + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + var proj = new Project (root); + root.FullPath = "ProjectTest.BuildCSharpTargetGetFrameworkPaths.proj"; + Assert.IsTrue (proj.Build ("GetFrameworkPaths", new ILogger [] {/*new ConsoleLogger ()*/})); + } + + [Test] + public void BuildCSharpTargetBuild () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <PropertyGroup> + <AssemblyName>Foo</AssemblyName> + </PropertyGroup> + <Import Project='$(MSBuildToolsPath)\Microsoft.CSharp.targets' /> +</Project>"; + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + root.FullPath = "ProjectTest.BuildCSharpTargetBuild.proj"; + var proj = new Project (root, null, "4.0"); + Assert.IsFalse (proj.Build ("Build", new ILogger [] {/*new ConsoleLogger (LoggerVerbosity.Diagnostic)*/})); // missing mandatory properties + } + + [Test] + public void EvaluateItemConditionThenIgnored () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <PropertyGroup> + <P></P> + </PropertyGroup> + <ItemGroup> + <Foo Condition='' Include='x' /> + <Bar Include='$(P)' /> + <Baz Include='z' /> + </ItemGroup> +</Project>"; + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + var proj = new Project (root); + // note that Foo is ignored BUT Bar is NOT ignored. + Assert.AreEqual (2, proj.ItemsIgnoringCondition.Count, "#1"); + Assert.IsNotNull ("Bar", proj.ItemsIgnoringCondition.First ().ItemType, "#2"); + Assert.IsNotNull ("Baz", proj.ItemsIgnoringCondition.Last ().ItemType, "#3"); + } + + [Test] + public void EvaluateSamePropertiesInOrder () + { + // used in Microsoft.Common.targets + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <PropertyGroup> + <BaseIntermediateOutputPath Condition=""'$(BaseIntermediateOutputPath)' == ''"">obj\</BaseIntermediateOutputPath> + </PropertyGroup> +</Project>"; + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + var proj = new Project (root); + Assert.AreEqual ("obj\\", proj.GetPropertyValue ("BaseIntermediateOutputPath"), "#1"); + } + + [Test] + public void DirtyMarking () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' />"; + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + var proj = new Project (root); + Assert.IsFalse (proj.IsDirty, "#1"); + proj.MarkDirty (); + Assert.IsTrue (proj.IsDirty, "#2"); + } + + [Test] + public void DirtyMarking2 () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' />"; + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + var proj = new Project (root); + proj.DisableMarkDirty = true; + proj.MarkDirty (); + Assert.IsFalse (proj.IsDirty, "#1"); // not rejected, just ignored. + proj.DisableMarkDirty = false; + Assert.IsFalse (proj.IsDirty, "#2"); // not like status pending + proj.MarkDirty (); + Assert.IsTrue (proj.IsDirty, "#3"); + } + + [Test] + public void CreateProjectInstance () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <PropertyGroup> + <AssemblyName>Foo</AssemblyName> + </PropertyGroup> + <Import Project='$(MSBuildToolsPath)\Microsoft.CSharp.targets' /> +</Project>"; + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + var proj = new Project (root, null, "4.0"); + var inst = proj.CreateProjectInstance (); + Assert.AreEqual ("4.0", inst.ToolsVersion, "#1"); + } + } +} + diff --git a/mcs/class/Microsoft.Build/Test/Microsoft.Build.Evaluation/ResolvedImportTest.cs b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Evaluation/ResolvedImportTest.cs new file mode 100644 index 0000000000..9c4359ee7a --- /dev/null +++ b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Evaluation/ResolvedImportTest.cs @@ -0,0 +1,181 @@ +// +// ResolvedImportTest.cs +// +// Author: +// Atsushi Enomoto (atsushi@xamarin.com) +// +// Copyright (C) 2013 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.IO; +using System.Linq; +using System.Xml; +using Microsoft.Build.Construction; +using Microsoft.Build.Evaluation; +using NUnit.Framework; +using Microsoft.Build.Exceptions; +using Microsoft.Build.Framework; +using Microsoft.Build.Execution; + +namespace MonoTests.Microsoft.Build.Evaluation +{ + [TestFixture] + public class ResolvedImportTest + { + const string temp_file_name = "test_imported.proj"; + + [Test] + public void SimpleImportAndSemanticValues () + { + string xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <Import Project='test_imported.proj' /> +</Project>"; + string imported = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <PropertyGroup> + <A>x</A> + <B>y</B> + </PropertyGroup> + <ItemGroup> + <X Include=""included.txt"" /> + </ItemGroup> +</Project>"; + using (var ts = File.CreateText (temp_file_name)) + ts.Write (imported); + try { + var reader = XmlReader.Create (new StringReader (xml)); + var root = ProjectRootElement.Create (reader); + Assert.AreEqual (temp_file_name, root.Imports.First ().Project, "#1"); + var proj = new Project (root); + var prop = proj.GetProperty ("A"); + Assert.IsNotNull (prop, "#2"); + Assert.IsTrue (prop.IsImported, "#3"); + var item = proj.GetItems ("X").FirstOrDefault (); + Assert.IsNotNull (item, "#4"); + Assert.AreEqual ("included.txt", item.EvaluatedInclude, "#5"); + } finally { + File.Delete (temp_file_name); + } + } + + string import_overrides_test_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <PropertyGroup> + <A>X</A> + </PropertyGroup> + <Import Condition=""{0}"" Project='test_imported.proj' /> + <PropertyGroup> + <B>Y</B> + </PropertyGroup> +</Project>"; + string import_overrides_test_imported = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <PropertyGroup> + <C Condition='$(A)==x'>c</C> + <A>a</A> + <B>b</B> + </PropertyGroup> + <ItemGroup> + <X Include=""included.txt"" /> + </ItemGroup> +</Project>"; + + void ImportAndPropertyOverrides (string label, string condition, string valueA, string valueB, string valueAPredecessor, bool existsC) + { + using (var ts = File.CreateText (temp_file_name)) + ts.Write (import_overrides_test_imported); + try { + string xml = string.Format (import_overrides_test_xml, condition); + var reader = XmlReader.Create (new StringReader (xml)); + var root = ProjectRootElement.Create (reader); + var proj = new Project (root); + var a = proj.GetProperty ("A"); + Assert.IsNotNull (a, label + "#2"); + Assert.AreEqual (valueA, a.EvaluatedValue, label + "#3"); + if (valueAPredecessor == null) + Assert.IsNull (a.Predecessor, label + "#3.1"); + else { + Assert.IsNotNull (a.Predecessor, label + "#3.2"); + Assert.AreEqual (valueAPredecessor, a.Predecessor.EvaluatedValue, label + "#3.3"); + } + var b = proj.GetProperty ("B"); + Assert.IsNotNull (b, label + "#4"); + Assert.AreEqual (valueB, b.EvaluatedValue, label + "#5"); + var c = proj.GetProperty ("C"); // yes it can be retrieved. + if (existsC) { + Assert.IsNotNull (c, label + "#6"); + Assert.AreEqual ("c", c.EvaluatedValue, label + "#7"); + } + else + Assert.IsNull (c, label + "#8"); + } finally { + File.Delete (temp_file_name); + } + } + + [Test] + public void ImportAndPropertyOverrides () + { + ImportAndPropertyOverrides ("[1]", "'True'", "a", "Y", "X", true); + ImportAndPropertyOverrides ("[2]", "$(A)=='X'", "a", "Y", "X", true); // evaluated as true + ImportAndPropertyOverrides ("[3]", "$(B)=='Y'", "X", "Y", null, false); // evaluated as false + ImportAndPropertyOverrides ("[4]", "$(B)=='b'", "X", "Y", null, false); // of course not evaluated with imported value + } + + // FIXME: + // Looks like $(MSBuildThisFile) is available only within property value, not via .NET MSBuild API. + // Right now our variable is added as a Reserved property, but we will have to hide it. + // + [Test] + public void EvaluateMSBuildThisFileProperty () + { + string xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <PropertyGroup> + <A>$(MSBuildThisFile)</A> + </PropertyGroup> + <Import Project='test_imported.proj' /> +</Project>"; + string imported = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <PropertyGroup> + <B>$(MSBuildThisFile)</B> + </PropertyGroup> +</Project>"; + using (var ts = File.CreateText (temp_file_name)) + ts.Write (imported); + try { + var reader = XmlReader.Create (new StringReader (xml)); + var root = ProjectRootElement.Create (reader); + var proj = new Project (root); + var a = proj.GetProperty ("A"); + Assert.AreEqual (string.Empty, a.EvaluatedValue, "#1"); + var b = proj.GetProperty ("B"); + Assert.AreEqual (temp_file_name, b.EvaluatedValue, "#2"); + + var pi = new ProjectInstance (root); + var ai = pi.GetProperty ("A"); + Assert.AreEqual (string.Empty, ai.EvaluatedValue, "#3"); + var bi = pi.GetProperty ("B"); + Assert.AreEqual (temp_file_name, bi.EvaluatedValue, "#4"); + } finally { + File.Delete (temp_file_name); + } + } + } +} + diff --git a/mcs/class/Microsoft.Build/Test/Microsoft.Build.Evaluation/ToolsetTest.cs b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Evaluation/ToolsetTest.cs new file mode 100644 index 0000000000..ac4e2d6610 --- /dev/null +++ b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Evaluation/ToolsetTest.cs @@ -0,0 +1,53 @@ +// +// ToolSetTest.cs +// +// Author: +// Atsushi Enomoto (atsushi@xamarin.com) +// +// Copyright (C) 2013 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 Microsoft.Build.Evaluation; +using NUnit.Framework; + +namespace MonoTests.Microsoft.Build.Evaluation +{ + [TestFixture] + public class ToolsetTest + { + [Test] + public void Constructor () + { + var ts = new Toolset ("4.3", "c:\\", ProjectCollection.GlobalProjectCollection, null); + Assert.IsNotNull (ts.Properties, "#1"); + Assert.AreEqual (0, ts.Properties.Count, "#2"); +#if NET_4_5 + Assert.IsNull (ts.DefaultSubToolsetVersion, "#3"); + Assert.IsNotNull (ts.SubToolsets, "#4"); + Assert.AreEqual (0, ts.SubToolsets.Count, "#5"); +#endif + Assert.AreEqual ("c:\\", ts.ToolsPath, "#6"); + Assert.AreEqual ("4.3", ts.ToolsVersion, "#7"); + } + } +} + diff --git a/mcs/class/Microsoft.Build/Test/Microsoft.Build.Execution/BuildManagerTest.cs b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Execution/BuildManagerTest.cs new file mode 100644 index 0000000000..0fcbdf7560 --- /dev/null +++ b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Execution/BuildManagerTest.cs @@ -0,0 +1,201 @@ +// +// BuildManagerTest.cs +// +// Author: +// Atsushi Enomoto (atsushi@xamarin.com) +// +// Copyright (C) 2013 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.IO; +using System.Xml; +using Microsoft.Build.Construction; +using Microsoft.Build.Evaluation; +using Microsoft.Build.Execution; +using NUnit.Framework; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Build.Framework; +using Microsoft.Build.Logging; + +namespace MonoTests.Microsoft.Build.Execution +{ + [TestFixture] + public class BuildManagerTest + { + Project GetDummyProject () + { + string empty_project_xml = "<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' />"; + var path = "file://localhost/foo.xml"; + var xml = XmlReader.Create (new StringReader (empty_project_xml), null, path); + var root = ProjectRootElement.Create (xml); + return new Project (root); + } + + [Test] + [ExpectedException (typeof (ArgumentNullException))] + public void GetProjectInstanceForBuildNullFullPath () + { + var manager = new BuildManager (); + manager.GetProjectInstanceForBuild (GetDummyProject ()); + } + + [Test] + [ExpectedException (typeof (ArgumentException))] + public void GetProjectInstanceForBuildEmptyFullPath () + { + var proj = GetDummyProject (); + proj.FullPath = ""; + var manager = new BuildManager (); + manager.GetProjectInstanceForBuild (proj); + } + + [Test] + public void GetProjectInstanceForBuild () + { + string empty_project_xml = "<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' />"; + var path = "file://localhost/foo.xml"; + var xml = XmlReader.Create (new StringReader(empty_project_xml), null, path); + var root = ProjectRootElement.Create (xml); + root.FullPath = path; + var proj = new Project (root); + var manager = new BuildManager (); + var inst = manager.GetProjectInstanceForBuild (proj); + Assert.AreEqual (inst, manager.GetProjectInstanceForBuild (proj), "#1"); + } + + [Test] + [ExpectedException (typeof (InvalidOperationException))] + public void PendBuildRequestBeforeBeginBuild () + { + string empty_project_xml = "<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' />"; + var path = "file://localhost/foo.xml"; + var xml = XmlReader.Create (new StringReader (empty_project_xml), null, path); + var root = ProjectRootElement.Create (xml); + var proj = new ProjectInstance (root); + new BuildManager ().PendBuildRequest (new BuildRequestData (proj, new string [0])); + } + + [Test] + [ExpectedException (typeof (InvalidOperationException))] + public void ResetCachesDuringBuildIsInvalid () + { + // Windows does not have useful sleep or alternative, so skip it + bool is_windows = true; + switch (Environment.OSVersion.Platform) { + case PlatformID.Unix: + case PlatformID.MacOSX: + is_windows = false; + break; + } + string project_xml = string.Format (@"<Project DefaultTargets='Wait1Sec' xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <Target Name='Wait1Sec'> + <Exec Command='{0}' /> + </Target> +</Project>", is_windows ? "powershell -command \"Start-Sleep -s 1\"" : "/bin/sleep 1"); + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + var proj = new ProjectInstance (root); + var bm = new BuildManager (); + bm.BeginBuild (new BuildParameters ()); + var sub = bm.PendBuildRequest (new BuildRequestData (proj, new string [] { "Wait1Sec" })); + sub.ExecuteAsync (delegate {}, null); + try { + bm.ResetCaches (); + } finally { + bm.EndBuild (); // yes, it should work even after invalid ResetCaches call... at least on .NET it does. + } + } + + [Test] + public void BasicManualParallelBuilds () + { + string project_xml = @"<Project DefaultTargets='Wait1Sec' xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <Target Name='Wait1Sec'> + <!-- Exec Command='ping 10.1.1.1 -n 1 -w 1' /--> + <Exec Command='/bin/sleep 1' /> + </Target> +</Project>"; + switch (Environment.OSVersion.Platform) { + case PlatformID.MacOSX: + case PlatformID.Unix: + break; + default: + return; // ignore, cannot run it + } + + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + var proj = new ProjectInstance (root); + var bm = new BuildManager (); + bm.BeginBuild (new BuildParameters () { Loggers = new ILogger [] {new ConsoleLogger (LoggerVerbosity.Diagnostic, TextWriter.Null.WriteLine, null, null)} }); + DateTime waitDone = DateTime.MinValue; + DateTime beforeExec = DateTime.Now; + var l = new List<BuildSubmission> (); + for (int i = 0; i < 10; i++) { + var sub = bm.PendBuildRequest (new BuildRequestData (proj, new string [] { "Wait1Sec" })); + l.Add (sub); + sub.ExecuteAsync (delegate { waitDone = DateTime.Now; }, null); + } + bm.EndBuild (); + Assert.IsTrue (l.All (s => s.BuildResult.OverallResult == BuildResultCode.Success), "#1"); + DateTime endBuildDone = DateTime.Now; + Assert.IsTrue (endBuildDone - beforeExec >= TimeSpan.FromSeconds (1), "#2"); + Assert.IsTrue (endBuildDone > waitDone, "#3"); + } + + [Test] + public void BuildCommonResolveAssemblyReferences () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <Import Project='$(MSBuildToolsPath)\Microsoft.Common.targets' /> + <ItemGroup> + <Reference Include='System.Core' /> + <Reference Include='System.Xml' /> + </ItemGroup> +</Project>"; + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + root.FullPath = "BuildManagerTest.BuildCommonResolveAssemblyReferences.proj"; + var proj = new ProjectInstance (root); + var manager = new BuildManager (); + var parameters = new BuildParameters () { Loggers = new ILogger [] {new ConsoleLogger (LoggerVerbosity.Diagnostic)} }; + var request = new BuildRequestData (proj, new string [] {"ResolveAssemblyReferences"}); + Assert.AreEqual (string.Empty, proj.GetPropertyValue ("TargetFrameworkDirectory"), "#1-1"); + var result = manager.Build (parameters, request); + Assert.AreNotEqual (string.Empty, proj.GetPropertyValue ("TargetFrameworkDirectory"), "#1-2"); // filled during build. + Assert.IsTrue (result.ResultsByTarget.ContainsKey ("GetFrameworkPaths"), "#2-1"); + Assert.IsTrue (result.ResultsByTarget.ContainsKey ("PrepareForBuild"), "#2-2"); + Assert.IsTrue (result.ResultsByTarget.ContainsKey ("ResolveAssemblyReferences"), "#2-3"); + var items = proj.GetItems ("ReferencePath"); + Assert.AreEqual (2, items.Count (), "#3"); + var syscore = items.FirstOrDefault (i => Path.GetFileName (i.EvaluatedInclude) == "System.Core.dll"); + var sysxml = items.FirstOrDefault (i => Path.GetFileName (i.EvaluatedInclude) == "System.Xml.dll"); + Assert.IsNotNull (syscore, "#4-1"); + Assert.IsNotNull (sysxml, "#4-2"); + Assert.IsTrue (File.Exists (syscore.EvaluatedInclude), "#5-1"); + Assert.IsTrue (File.Exists (sysxml.EvaluatedInclude), "#5-1"); + Assert.AreEqual (BuildResultCode.Success, result.OverallResult, "#6"); + } + } +} + diff --git a/mcs/class/Microsoft.Build/Test/Microsoft.Build.Execution/BuildParametersTest.cs b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Execution/BuildParametersTest.cs new file mode 100644 index 0000000000..47cd5f9007 --- /dev/null +++ b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Execution/BuildParametersTest.cs @@ -0,0 +1,66 @@ +// +// BuildParametersTest.cs +// +// Author: +// Atsushi Enomoto (atsushi@xamarin.com) +// +// Copyright (C) 2013 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.Globalization; +using System.IO; +using System.Linq; +using Microsoft.Build.Evaluation; +using Microsoft.Build.Execution; +using NUnit.Framework; + +namespace MonoTests.Microsoft.Build.Execution +{ + [TestFixture] + public class BuildParametersTest + { + [Test] + public void GetToolset () + { + var bp = new BuildParameters (ProjectCollection.GlobalProjectCollection); + Assert.IsNull (bp.GetToolset ("0.1"), "#1"); + var ts = bp.GetToolset ("2.0"); + // They are equal + Assert.AreEqual (ProjectCollection.GlobalProjectCollection.Toolsets.First (t => t.ToolsVersion == "2.0"), ts, "#2"); + + bp = new BuildParameters (); + Assert.IsNull (bp.GetToolset ("0.1"), "#1"); + ts = bp.GetToolset ("2.0"); + // They are NOT equal, because ProjectCollection seems to be different. + Assert.AreNotEqual (ProjectCollection.GlobalProjectCollection.Toolsets.First (t => t.ToolsVersion == "2.0"), ts, "#2"); + } + + [Test] + public void PropertiesDefault () + { + var bp = new BuildParameters (); + Assert.IsTrue (bp.EnableNodeReuse, "#1"); + Assert.IsTrue (bp.EnvironmentProperties.Count > 0, "#2"); + Assert.AreEqual (CultureInfo.CurrentCulture, bp.Culture, "#3"); + } + } +} diff --git a/mcs/class/Microsoft.Build/Test/Microsoft.Build.Execution/BuildSubmissionTest.cs b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Execution/BuildSubmissionTest.cs new file mode 100644 index 0000000000..df2febcbf7 --- /dev/null +++ b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Execution/BuildSubmissionTest.cs @@ -0,0 +1,115 @@ +// +// BuildSubmissionTest.cs +// +// Author: +// Atsushi Enomoto (atsushi@xamarin.com) +// +// Copyright (C) 2013 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.IO; +using System.Linq; +using System.Xml; +using Microsoft.Build.Construction; +using Microsoft.Build.Evaluation; +using Microsoft.Build.Execution; +using NUnit.Framework; +using Microsoft.Build.Logging; +using Microsoft.Build.Framework; +using System.Collections.Generic; + +namespace MonoTests.Microsoft.Build.Execution +{ + [TestFixture] + public class BuildSubmissionTest + { + [Test] + public void ResultBeforeExecute () + { + string empty_project_xml = "<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' />"; + var path = "file://localhost/foo.xml"; + var xml = XmlReader.Create (new StringReader (empty_project_xml), null, path); + var root = ProjectRootElement.Create (xml); + var proj = new ProjectInstance (root); + var bm = new BuildManager (); + bm.BeginBuild (new BuildParameters ()); + var sub = bm.PendBuildRequest (new BuildRequestData (proj, new string [0])); + Assert.IsNull (sub.BuildResult, "#1"); + } + + // This checks if the build output for each task is written to the loggers and not directly thrown as a Project loader error. + [Test] + public void TaskOutputsToLoggers () + { + string project_xml = @"<Project DefaultTargets='Foo' xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <Import Project='$(MSBuildToolsPath)\Microsoft.Common.targets' /> + <Target Name='Foo'> + <ItemGroup> + <Foo Condition='$(X)' Include='foo.txt' /> + </ItemGroup> + </Target> +</Project>"; + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + root.FullPath = "BuildSubmissionTest.TaskOutputsToLoggers.proj"; + var proj = new ProjectInstance (root); + Assert.AreEqual ("$(X)", root.Targets.First ().ItemGroups.First ().Items.First ().Condition, "#0"); + var sw = new StringWriter (); + Assert.IsFalse (proj.Build (new ILogger [] {new ConsoleLogger (LoggerVerbosity.Diagnostic, sw.WriteLine, null, null)}), "#1"); + Assert.IsTrue (sw.ToString ().Contains ("$(X)"), "#2"); + } + + [Test] + public void EndBuildWaitsForSubmissionCompletion () + { + // Windows does not have useful sleep or alternative, so skip it + bool is_windows = true; + switch (Environment.OSVersion.Platform) { + case PlatformID.Unix: + case PlatformID.MacOSX: + is_windows = false; + break; + } + string project_xml = string.Format (@"<Project DefaultTargets='Wait1Sec' xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <Target Name='Wait1Sec'> + <Exec Command='{0}' /> + </Target> +</Project>", is_windows ? "powershell -command \"Start-Sleep -s 1\"" : "/bin/sleep 1"); + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + root.FullPath = "BuildSubmissionTest.EndBuildWaitsForSubmissionCompletion.proj"; + var proj = new ProjectInstance (root); + var bm = new BuildManager (); + bm.BeginBuild (new BuildParameters ()); + DateTime waitDone = DateTime.MinValue; + DateTime beforeExec = DateTime.Now; + var sub = bm.PendBuildRequest (new BuildRequestData (proj, new string [] { "Wait1Sec" })); + sub.ExecuteAsync (delegate { waitDone = DateTime.Now; }, null); + bm.EndBuild (); + Assert.IsTrue (sub.BuildResult.OverallResult == BuildResultCode.Success, "#1"); + DateTime endBuildDone = DateTime.Now; + Assert.IsTrue (endBuildDone - beforeExec >= TimeSpan.FromSeconds (1), "#2"); + Assert.IsTrue (endBuildDone > waitDone, "#3"); + } + } +} + diff --git a/mcs/class/Microsoft.Build/Test/Microsoft.Build.Execution/ProjectInstanceTest.cs b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Execution/ProjectInstanceTest.cs new file mode 100644 index 0000000000..327aa389c4 --- /dev/null +++ b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Execution/ProjectInstanceTest.cs @@ -0,0 +1,103 @@ +// +// ProjectInstanceTest.cs +// +// Author: +// Atsushi Enomoto (atsushi@xamarin.com) +// +// Copyright (C) 2013 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.IO; +using System.Linq; +using System.Xml; +using Microsoft.Build.Construction; +using Microsoft.Build.Execution; +using NUnit.Framework; +using Microsoft.Build.Evaluation; + +namespace MonoTests.Microsoft.Build.Execution +{ + [TestFixture] + public class ProjectInstanceTest + { + [Test] + public void ItemsAndProperties () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <ItemGroup> + <X Condition='false' Include='bar.txt' /> + <X Include='foo.txt'> + <M>m</M> + <N>=</N> + </X> + </ItemGroup> + <PropertyGroup> + <P Condition='false'>void</P> + <P Condition='true'>valid</P> + </PropertyGroup> +</Project>"; + var xml = XmlReader.Create (new StringReader(project_xml)); + var root = ProjectRootElement.Create (xml); + var proj = new ProjectInstance (root); + var item = proj.Items.First (); + Assert.AreEqual ("foo.txt", item.EvaluatedInclude, "#1"); + var prop = proj.Properties.First (p => p.Name=="P"); + Assert.AreEqual ("valid", prop.EvaluatedValue, "#2"); + Assert.IsNotNull (proj.GetProperty ("MSBuildProjectDirectory"), "#3"); + Assert.AreEqual ("2.0", proj.ToolsVersion, "#4"); + } + + [Test] + public void ExplicitToolsVersion () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' />"; + var xml = XmlReader.Create (new StringReader(project_xml)); + var root = ProjectRootElement.Create (xml); + var proj = new ProjectInstance (root, null, "4.0", new ProjectCollection ()); + Assert.AreEqual ("4.0", proj.ToolsVersion, "#1"); + } + + [Test] + public void BuildEmptyProject () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' />"; + var xml = XmlReader.Create (new StringReader (project_xml), null, "file://localhost/foo.xml"); + var root = ProjectRootElement.Create (xml); + // This seems to do nothing and still returns true + root.FullPath = "ProjectInstanceTest.BuildEmptyProject.1.proj"; + Assert.IsTrue (new ProjectInstance (root).Build (), "#1"); + // This seems to fail to find the appropriate target + root.FullPath = "ProjectInstanceTest.BuildEmptyProject.2.proj"; + Assert.IsFalse (new ProjectInstance (root).Build ("Build", null), "#2"); + // Thus, this tries to build all the targets (empty) and no one failed, so returns true(!) + root.FullPath = "ProjectInstanceTest.BuildEmptyProject.3.proj"; + Assert.IsTrue (new ProjectInstance (root).Build (new string [0], null), "#3"); + // Actially null "targets" is accepted and returns true(!!) + root.FullPath = "ProjectInstanceTest.BuildEmptyProject.4.proj"; + Assert.IsTrue (new ProjectInstance (root).Build ((string []) null, null), "#4"); + // matching seems to be blindly done, null string also results in true(!!) + root.FullPath = "ProjectInstanceTest.BuildEmptyProject.5.proj"; + Assert.IsTrue (new ProjectInstance (root).Build ((string) null, null), "#5"); + } + } +} + diff --git a/mcs/class/Microsoft.Build/Test/Microsoft.Build.Execution/ProjectMetadataInstanceTest.cs b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Execution/ProjectMetadataInstanceTest.cs new file mode 100644 index 0000000000..5c136d6b60 --- /dev/null +++ b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Execution/ProjectMetadataInstanceTest.cs @@ -0,0 +1,78 @@ +// +// ProjectMetadataInstanceTest.cs +// +// Author: +// Atsushi Enomoto (atsushi@xamarin.com) +// +// Copyright (C) 2013 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.IO; +using System.Linq; +using System.Xml; +using Microsoft.Build.Construction; +using Microsoft.Build.Execution; +using NUnit.Framework; + +namespace MonoTests.Microsoft.Build.Execution +{ + [TestFixture] + public class ProjectMetadataInstanceTest + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <ItemGroup> + <X Include='foo.txt'> + <M>m</M> + <N>=</N> + </X> + </ItemGroup> +</Project>"; + + [Test] + public void PropertiesCopiesValues () + { + var xml = XmlReader.Create (new StringReader (project_xml)); + string path = Path.GetFullPath ("foo.xml"); + var root = ProjectRootElement.Create (xml); + var proj = new ProjectInstance (root); + var item = proj.Items.First (); + var md = item.Metadata.First (); + Assert.AreEqual ("m", item.Metadata.First ().EvaluatedValue, "#1"); + Assert.AreEqual ("m", root.ItemGroups.First ().Items.First ().Metadata.First ().Value, "#2"); + root.ItemGroups.First ().Items.First ().Metadata.First ().Value = "X"; + Assert.AreEqual ("m", item.Metadata.First ().EvaluatedValue, "#3"); + } + + [Test] + public void ToStringOverride () + { + var xml = XmlReader.Create (new StringReader (project_xml)); + string path = Path.GetFullPath ("foo.xml"); + var root = ProjectRootElement.Create (xml); + var proj = new ProjectInstance (root); + var item = proj.Items.First (); + Assert.AreEqual ("M=m", item.Metadata.First ().ToString (), "#1"); + Assert.AreEqual ("N==", item.Metadata.Last ().ToString (), "#2"); // haha + } + } +} + diff --git a/mcs/class/Microsoft.Build/Test/Microsoft.Build.Execution/ProjectTargetInstanceTest.cs b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Execution/ProjectTargetInstanceTest.cs new file mode 100644 index 0000000000..2ae7148c8f --- /dev/null +++ b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Execution/ProjectTargetInstanceTest.cs @@ -0,0 +1,172 @@ +// +// ProjectTargetInstanceTest.cs +// +// Author: +// Atsushi Enomoto (atsushi@xamarin.com) +// +// Copyright (C) 2013 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.IO; +using System.Linq; +using System.Xml; +using Microsoft.Build.Construction; +using Microsoft.Build.Execution; +using NUnit.Framework; +using Microsoft.Build.Logging; +using Microsoft.Build.Framework; + +namespace MonoTests.Microsoft.Build.Execution +{ + [TestFixture] + public class ProjectTargetInstanceTest + { + [Test] + public void DefaultTargetsEmpty () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> +</Project>"; + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + var proj = new ProjectInstance (root); + Assert.AreEqual (new string [0], proj.DefaultTargets, "#1"); + } + + [Test] + public void DefaultTargetsFromAttribute () + { + string project_xml = @"<Project DefaultTargets='Foo Bar Baz;Foo' xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> +</Project>"; + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + var proj = new ProjectInstance (root); + string [] expected = {"Foo Bar Baz", "Foo"}; + Assert.AreEqual (expected, proj.DefaultTargets, "#1"); + } + + [Test] + public void DefaultTargetsFromElements () + { + string [] defaultTargetAtts = {string.Empty, "DefaultTargets=''"}; + + for (int i = 0; i < defaultTargetAtts.Length; i++) { + string project_xml = string.Format (@"<Project {0} xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <Target Name='Foo' /> + <Target Name='Bar' /> +</Project>", defaultTargetAtts [i]); + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + var proj = new ProjectInstance (root); + string [] expected = {"Foo"}; // Bar is not included + Assert.AreEqual (expected, proj.DefaultTargets, "#1-" + i); + } + } + + [Test] + public void MicrosoftCommonTargets () + { + string [] defaultTargetAtts = { string.Empty, "DefaultTargets=''" }; + + for (int i = 0; i < defaultTargetAtts.Length; i++) { + string project_xml = string.Format (@"<Project {0} xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <Import Project='$(MSBuildToolsPath)\Microsoft.Common.targets' /> +</Project>", defaultTargetAtts [i]); + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + var proj = new ProjectInstance (root); + Assert.AreEqual ("Build", proj.DefaultTargets.FirstOrDefault (), "#1-" + i); + } + } + + [Test] + public void DefaultTargetsOverride () + { + string project_xml = @"<Project DefaultTargets='Foo' xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <Import Project='$(MSBuildToolsPath)\Microsoft.Common.targets' /> +</Project>"; + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + var proj = new ProjectInstance (root); + Assert.AreEqual ("Foo", proj.DefaultTargets.FirstOrDefault (), "#1"); + } + + [Test] + public void MultipleDefaultTargets () + { + bool[] expected = { true, false, true }; + string [] defaultTargets = {"Foo", "Foo;Bar", "Foo;Bar"}; + string [] targets = { string.Empty, string.Empty, "<Target Name='Bar' />" }; + for (int i = 0; i < expected.Length; i++) { + string project_xml = string.Format (@"<Project DefaultTargets='{0}' xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <Import Project='$(MSBuildToolsPath)\Microsoft.Common.targets' /> + <Target Name='Foo' /> + {1} +</Project>", defaultTargets [i], targets [i]); + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + root.FullPath = string.Format ("ProjectInstanceTest.MultipleDefaultTargets.{0}.proj", i); + var proj = new ProjectInstance (root); + Assert.AreEqual ("Foo", proj.DefaultTargets.FirstOrDefault (), "#1-" + i); + Assert.AreEqual (expected [i], proj.Build (), "#2-" + i); + } + } + + [Test] + public void DependsOnTargets () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <Target Name='Bar' DependsOnTargets='Foo' /> + <Target Name='Foo'> + <Error Text='expected error' /> + </Target> +</Project>"; + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + root.FullPath = "ProjectInstanceTest.DependsOnTargets.proj"; + var proj = new ProjectInstance (root); + Assert.AreEqual (2, proj.Targets.Count, "#1"); + Assert.IsFalse (proj.Build ("Bar", new ILogger [0]), "#2"); + } + + [Test] + public void InputsAndOutputs () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <Target Name='Foo' Inputs='inputsandoutputstest.txt' Outputs='inputsandoutputstest.txt'> + <Error Text='error' /> + </Target> +</Project>"; + try { + if (!File.Exists ("inputsandoutputstest.txt")) + File.CreateText ("inputsandoutputstest.txt").Close (); + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + root.FullPath = "ProjectTargetInstanceTest.InputsAndOutputs.proj"; + var proj = new ProjectInstance (root); + Assert.IsTrue (proj.Build (), "#1"); // if it does not skip Foo, it results in an error. + } finally { + if (File.Exists ("inputsandoutputstest.txt")) + File.Delete ("inputsandoutputstest.txt"); + } + } + } +} diff --git a/mcs/class/Microsoft.Build/Test/Microsoft.Build.Internal/ExpressionParserTest.cs b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Internal/ExpressionParserTest.cs new file mode 100644 index 0000000000..7654538a1a --- /dev/null +++ b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Internal/ExpressionParserTest.cs @@ -0,0 +1,287 @@ +// +// ExpressionParserTest.cs +// +// Author: +// Atsushi Enomoto (atsushi@xamarin.com) +// +// Copyright (C) 2013 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.IO; +using System.Linq; +using System.Xml; +using Microsoft.Build.Construction; +using Microsoft.Build.Evaluation; +using NUnit.Framework; +using Microsoft.Build.Execution; +using Microsoft.Build.Exceptions; +using System.Collections.Generic; + +namespace MonoTests.Microsoft.Build.Internal +{ + [TestFixture] + public class ExpressionParserTest + { + string [] invalid_always = { + "$(Foo..Bar)", + "$([DateTime.Now])", // fullname required + "$([System.DateTime.Now])", // member cannot be invoked with '.' + }; + string [] invalid_as_boolean = { + "$", + "@", + "%", + "%-1", + "$(", + "%(", + "$)", + "%)", + "%24", + "()", + "{}", + "A", // must be evaluated as a boolean + "1", // ditto (no default conversion to bool) + "$ (foo) == ''", + "@ (foo) == ''", + "$(1)", + "$(Foo) == And", // reserved keyword 'and' + "$(Foo) == Or", // reserved keyword 'or' + "$(Foo) == $(Bar) == $(Baz)", // unexpected '==' + "$([System.DateTime]::Now)", // it is DateTime + "$([System.String]::Format('Tr'))$([System.String]::Format('ue'))", // only one expression is accepted + "$([System.String]::Format(null))", // causing ANE, wrapped by InvalidProjectFileException + "yep", + "nope", + "ONN", + "OFFF", + }; + string [] valid = { + "'%24' == 0", + "true", + "fAlSe", + "(false)", + "A==A", + "A ==A", + "A== A", + "A=='A'", + "A==\tA", + "\tA== A", + "$([System.String]::Format('True'))", + "$([System.String]::Format('True', null))", + "False And True == True And True", + "True or True or False", + "(True or True or False)", + "True and False", + "(True) and (False)", + "yes", + "nO", + "oN", + "oFf", + }; + string [] depends = { + // valid only if evaluated to boolean + "$(foo)", + "@(foo)", + }; + + [Test] + public void EvaluateAsBoolean () + { + foreach (var expr in invalid_always.Concat (invalid_as_boolean).Concat (valid)) { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <ItemGroup> + <Foo Condition=""{0}"" Include='x' /> + </ItemGroup> +</Project>"; + var xml = XmlReader.Create (new StringReader (string.Format (project_xml, expr))); + var root = ProjectRootElement.Create (xml); + try { + new Project (root); + if (invalid_as_boolean.Contains (expr) || invalid_always.Contains (expr)) + Assert.Fail ("Parsing Condition '{0}' should fail", expr); + } catch (Exception ex) { + if (valid.Contains (expr)) + throw new Exception (string.Format ("failed to parse '{0}'", expr), ex); + else if (ex is InvalidProjectFileException) + continue; + throw new Exception (string.Format ("unexpected exception to parse '{0}'", expr), ex); + } + } + } + + [Test] + public void EvaluateAsString () + { + foreach (var expr in invalid_always.Concat (invalid_as_boolean).Concat (valid)) { + try { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <ItemGroup> + <Foo Include=""{0}"" /> + </ItemGroup> + </Project>"; + var xml = XmlReader.Create (new StringReader (string.Format (project_xml, expr))); + var root = ProjectRootElement.Create (xml); + // everything but 'invalid_always' should pass + new Project (root); + } catch (Exception ex) { + if (!invalid_always.Contains (expr)) + throw new Exception (string.Format ("failed to parse '{0}'", expr), ex); + } + } + } + + [Test] + public void EvaluatePropertyReferencesWithProperties () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <ItemGroup> + <Foo Condition=""$(foo)"" Include='x' /> + </ItemGroup> +</Project>"; + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + var props = new Dictionary<string,string> (); + props ["foo"] = "true"; + new Project (root, props, null); + } + + [Test] + public void EvaluateItemReferences () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <ItemGroup> + <Foo Include='false' /> + <!-- by the time Bar is evaluated, Foo is already evaluated and taken into consideration in expansion --> + <Bar Condition=""@(foo)"" Include='x' /> + </ItemGroup> +</Project>"; + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + new Project (root); + } + + [Test] + public void EvaluateReferencesWithoutProperties () + { + foreach (var expr in depends) { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <ItemGroup> + <Foo Condition=""{0}"" Include='x' /> + </ItemGroup> +</Project>"; + var xml = XmlReader.Create (new StringReader (string.Format (project_xml, expr))); + var root = ProjectRootElement.Create (xml); + try { + new Project (root); + Assert.Fail ("Parsing Condition '{0}' should fail", expr); + } catch (InvalidProjectFileException) { + continue; + } + } + } + + [Test] + public void SemicolonHandling () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <PropertyGroup> + <Foo Condition=""'A;B'=='A;B'"">'A;B'</Foo> + </PropertyGroup> + <ItemGroup> + <Bar Include='$(Foo)' /> + </ItemGroup> +</Project>"; + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + var proj = new Project (root); // at this state property is parsed without error i.e. Condition evaluates fine. + var prop = proj.GetProperty ("Foo"); + Assert.AreEqual ("'A;B'", prop.EvaluatedValue, "#1"); + var items = proj.GetItems ("Bar"); + Assert.AreEqual ("'A", items.First ().EvaluatedInclude, "#2"); + Assert.AreEqual ("$(Foo)", items.First ().UnevaluatedInclude, "#3"); + Assert.AreEqual (2, items.Count, "#4"); + Assert.AreEqual ("B'", items.Last ().EvaluatedInclude, "#5"); + Assert.AreEqual ("$(Foo)", items.Last ().UnevaluatedInclude, "#6"); + Assert.IsTrue (items.First ().Xml == items.Last ().Xml, "#7"); + } + + // the same as above except that ItemGroup goes first (and yet evaluated the same). + [Test] + public void EvaluationOrderPropertiesPrecedesItems () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <ItemGroup> + <Bar Include='$(Foo)' /> + </ItemGroup> + <PropertyGroup> + <Foo Condition=""'A;B'=='A;B'"">'A;B'</Foo> + </PropertyGroup> +</Project>"; + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + var proj = new Project (root); // at this state property is parsed without error i.e. Condition evaluates fine. + var prop = proj.GetProperty ("Foo"); + Assert.AreEqual ("'A;B'", prop.EvaluatedValue, "#1"); + var items = proj.GetItems ("Bar"); + Assert.AreEqual ("'A", items.First ().EvaluatedInclude, "#2"); + Assert.AreEqual ("$(Foo)", items.First ().UnevaluatedInclude, "#3"); + Assert.AreEqual (2, items.Count, "#4"); + Assert.AreEqual ("B'", items.Last ().EvaluatedInclude, "#5"); + Assert.AreEqual ("$(Foo)", items.Last ().UnevaluatedInclude, "#6"); + Assert.IsTrue (items.First ().Xml == items.Last ().Xml, "#7"); + } + + [Test] + [ExpectedException (typeof (InvalidProjectFileException))] + public void PropertyReferencesItem () + { + string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <ItemGroup> + <Bar Include='True' /> + </ItemGroup> + <PropertyGroup> + <Foo Condition='@(Bar)'>X</Foo><!-- not allowed --> + </PropertyGroup> +</Project>"; + var xml = XmlReader.Create (new StringReader (project_xml)); + var root = ProjectRootElement.Create (xml); + new Project (root); + } + + [Test] + [ExpectedException (typeof (InvalidProjectFileException))] + public void SequentialPropertyReferenceNotAllowed () + { + string xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> + <PropertyGroup> + <A>x</A> + <B>y</B> + <C Condition=""$(A)$(B)==''"">z</C> + </PropertyGroup> +</Project>"; + var reader = XmlReader.Create (new StringReader (xml)); + var root = ProjectRootElement.Create (reader); + new Project (root); + } + } +} + diff --git a/mcs/class/Microsoft.Build/Test/Microsoft.Build.Logging/ConsoleLoggerTest.cs b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Logging/ConsoleLoggerTest.cs new file mode 100644 index 0000000000..0135393d9e --- /dev/null +++ b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Logging/ConsoleLoggerTest.cs @@ -0,0 +1,102 @@ +// +// ConsoleLoggerTest.cs +// +// Author: +// Atsushi Enomoto (atsushi@xamarin.com) +// +// Copyright (C) 2013 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.IO; +using Microsoft.Build.Framework; +using Microsoft.Build.Logging; +using NUnit.Framework; + +namespace MonoTests.Microsoft.Build.Logging +{ + [TestFixture] + public class ConsoleLoggerTest + { + // Unfortunately, the existing code in MS.Build.Engine.dll has slightly different + // format. We'd rather use already complete implementation, just disabled this test. + [Test] + [Category ("NotWorking")] + public void BasicLoggerUsage () + { + string expected = @"file : cat error code: msg + +file : cat warning code: msg + +__________________________________________________ + +Project ""project.txt"" (target target(s)): + + + +Build started 2013/01/01 00:00:00. + +Target ""target"" in file ""target.txt"": + + start task + + finished task + +finished target + + + +finished project + + + +finished build + + + +Time Elapsed 00:00:00.01 + +".Replace ("\r\n", "\n"); + var sw = new StringWriter(); + var e = new ConsoleLogger(LoggerVerbosity.Diagnostic, msg => sw.WriteLine(msg), c => {}, () => {}); + e.Verbosity = LoggerVerbosity.Diagnostic; + e.ErrorHandler (null, new BuildErrorEventArgs ("cat", "code", "file", 0, 0, 0, 0, "msg", "help", "sender")); + e.WarningHandler (null, new BuildWarningEventArgs ("cat", "code", "file", 0, 0, 0, 0, "msg", "help", "sender")); + e.ProjectStartedHandler (null, new ProjectStartedEventArgs ("start project", "HELPME", "project.txt", "target", null, null)); + e.BuildStartedHandler (null, new BuildStartedEventArgs ("start build", "HELPME", new DateTime (2013, 1, 1))); + e.TargetStartedHandler (null, new TargetStartedEventArgs ("start target", "HELPME", "target", "project.txt", "target.txt"/*, "parent"*/)); + e.TaskStartedHandler (null, new TaskStartedEventArgs ("start task", "HELPME", "project.txt", "task.txt", "task")); + e.TaskFinishedHandler (null, new TaskFinishedEventArgs ("finished task", "HELPME", "project.txt", "task.txt", "task", false)); + e.TargetFinishedHandler (null, new TargetFinishedEventArgs ("finished target", "HELPME", "target", "project.txt", "target.txt", false)); + e.ProjectFinishedHandler (null, new ProjectFinishedEventArgs ("finished project", "HELPME", "project.txt", false)); + e.BuildFinishedHandler (null, new BuildFinishedEventArgs ("finished build", "HELPME", false, new DateTime (2013, 1, 1).AddMilliseconds (1))); + + e.CustomEventHandler(null, new MyCustomBuildEventArgs ()); + Assert.AreEqual (expected, sw.ToString ().Replace ("\r\n", "\n"), "#1"); + } + } + + class MyCustomBuildEventArgs : CustomBuildEventArgs + { + } +} + diff --git a/mcs/class/Microsoft.Build/Test/Microsoft.Build.Logging/LoggerDescriptionTest.cs b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Logging/LoggerDescriptionTest.cs new file mode 100644 index 0000000000..e062fc93b4 --- /dev/null +++ b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Logging/LoggerDescriptionTest.cs @@ -0,0 +1,49 @@ +// +// LoggerDescriptionTest.cs +// +// Author: +// Atsushi Eno (atsushi@xamarin.com) +// +// Copyright (C) 2013 Xamarin Inc. +// +// 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 Microsoft.Build.Logging; +using Microsoft.Build.Framework; +using NUnit.Framework; + +namespace MonoTests.Microsoft.Build.Logging +{ + [TestFixture] + public class LoggerDescriptionTest + { + [Test] + public void CreateLogger () + { + new LoggerDescription ("Microsoft.Build.Logging.ConsoleLogger", + typeof (ConsoleLogger).Assembly.FullName, + null, + null, + LoggerVerbosity.Normal) + .CreateLogger(); + } + } +} |