partal import implementation.
authorAtsushi Eno <atsushieno@veritas-vos-liberabit.com>
Tue, 22 Oct 2013 17:37:51 +0000 (02:37 +0900)
committerAtsushi Eno <atsushieno@veritas-vos-liberabit.com>
Fri, 29 Nov 2013 09:21:01 +0000 (18:21 +0900)
mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/Project.cs
mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ProjectCollection.cs
mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ProjectProperty.cs
mcs/class/Microsoft.Build/Microsoft.Build_test.dll.sources
mcs/class/Microsoft.Build/Test/Microsoft.Build.Evaluation/ResolvedImportTest.cs [new file with mode: 0644]

index 9b949c83555c469f4d3c2ca85987bd01083a2656..ae33aae3c13fd8e62ee2653c2d69be880721c681 100644 (file)
@@ -42,6 +42,7 @@ using Microsoft.Build.Execution;
 using Microsoft.Build.Framework;
 using Microsoft.Build.Logging;
 using System.Collections;
+using Microsoft.Build.Exceptions;
 
 namespace Microsoft.Build.Evaluation
 {
@@ -103,7 +104,18 @@ namespace Microsoft.Build.Evaluation
                        this.ProjectCollection = projectCollection;
                        this.load_settings = loadSettings;
 
-                       Initialize ();
+                       Initialize (null);
+               }
+               
+               Project (ProjectRootElement imported, Project parent)
+               {
+                       this.Xml = parent.Xml;
+                       this.GlobalProperties = parent.GlobalProperties;
+                       this.ToolsVersion = parent.ToolsVersion;
+                       this.ProjectCollection = parent.ProjectCollection;
+                       this.load_settings = parent.load_settings;
+
+                       Initialize (parent);
                }
 
                public Project (string projectFile)
@@ -149,47 +161,80 @@ namespace Microsoft.Build.Evaluation
                List<ProjectProperty> properties;
                Dictionary<string, ProjectTargetInstance> targets;
 
-               void Initialize ()
+               void Initialize (Project parent)
                {
                        dir_path = Directory.GetCurrentDirectory ();
                        raw_imports = new List<ResolvedImport> ();
                        item_definitions = new Dictionary<string, ProjectItemDefinition> ();
                        item_types = new List<string> ();
-                       properties = new List<ProjectProperty> ();
                        targets = new Dictionary<string, ProjectTargetInstance> ();
                        raw_items = new List<ProjectItem> ();
                        
-                       ProcessXml ();
+                       ProcessXml (parent);
                }
                
                static readonly char [] item_sep = {';'};
                
-               void ProcessXml ()
-               {
-                       foreach (DictionaryEntry p in Environment.GetEnvironmentVariables ())
-                               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 ReservedProjectProperty.GetReservedProperties (tools, this))
-                               this.properties.Add (p);
-                       foreach (var p in EnvironmentProjectProperty.GetWellKnownProperties (this))
-                               this.properties.Add (p);
+               void ProcessXml (Project parent)
+               {
+                       if (parent != null) {
+                               properties = parent.properties;
+                       } else {
+                               properties = new List<ProjectProperty> ();
+                       
+                               foreach (DictionaryEntry p in Environment.GetEnvironmentVariables ())
+                                       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 ReservedProjectProperty.GetReservedProperties (tools, this))
+                                       this.properties.Add (p);
+                               foreach (var p in EnvironmentProjectProperty.GetWellKnownProperties (this))
+                                       this.properties.Add (p);
+                       }
 
-                       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);
                        
+                       // next, evaluate items
+                       all_evaluated_items = new List<ProjectItem> ();
+
+                       EvaluateItems (elements);
+               }
+               
+               IEnumerable<ProjectElement> EvaluatePropertiesAndImports (IEnumerable<ProjectElement> elements)
+               {
                        // First step: evaluate Properties
-                       foreach (var child in Xml.Children) {
+                       foreach (var child in elements) {
+                               yield return child;
                                var pge = child as ProjectPropertyGroupElement;
                                if (pge != null && ShouldInclude (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 (ShouldInclude (p.Condition))
-                                                               this.properties.Add (new XmlProjectProperty (this, p, PropertyType.Normal));
+                                                               this.properties.Add (new XmlProjectProperty (this, p, PropertyType.Normal, ProjectCollection.OngoingImports.Any ()));
+
+                               var ige = child as ProjectImportGroupElement;
+                               if (ige != null && ShouldInclude (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 && ShouldInclude (inc.Condition))
+                                       foreach (var e in Import (inc))
+                                               yield return e;
                        }
-                       
-                       foreach (var child in Xml.Children) {
+               }
+
+               void EvaluateItems (IEnumerable<ProjectElement> elements)
+               {
+                       foreach (var child in elements) {
                                var ige = child as ProjectItemGroupElement;
                                if (ige != null) {
                                        foreach (var p in ige.Items) {
@@ -216,6 +261,29 @@ namespace Microsoft.Build.Evaluation
                        }
                        all_evaluated_items.Sort ((p1, p2) => string.Compare (p1.ItemType, p2.ItemType, StringComparison.OrdinalIgnoreCase));
                }
+               
+               IEnumerable<ProjectElement> Import (ProjectImportElement import)
+               {
+                       bool load = true;
+                       string path = Path.IsPathRooted (import.Project) ? import.Project : this.DirectoryPath != null ? Path.Combine (DirectoryPath, import.Project) : Path.GetFullPath (import.Project);
+                       if (ProjectCollection.OngoingImports.Contains (path)) {
+                               switch (load_settings) {
+                               case ProjectLoadSettings.RejectCircularImports:
+                                       throw new InvalidProjectFileException (import.Location, null, string.Format ("Circular imports was detected: {0} is already on \"importing\" stack", path));
+                               }
+                               yield break; // do not import circular references
+                       }
+                       ProjectCollection.OngoingImports.Push (path);
+                       try {
+                               var root = ProjectRootElement.Create (path, ProjectCollection);
+                               raw_imports.Add (new ResolvedImport (import, root, true));
+                               var imported = new Project (root, this);
+                               foreach (var e in imported.EvaluatePropertiesAndImports (imported.Xml.AllChildren))
+                                       yield return e;
+                       } finally {
+                               ProjectCollection.OngoingImports.Pop ();
+                       }
+               }
 
                public ICollection<ProjectItem> GetItemsIgnoringCondition (string itemType)
                {
@@ -476,7 +544,7 @@ namespace Microsoft.Build.Evaluation
                }
 
                public ICollection<ProjectProperty> AllEvaluatedProperties {
-                       get { throw new NotImplementedException (); }
+                       get { return properties; }
                }
 
                public IDictionary<string, List<string>> ConditionedProperties {
@@ -504,9 +572,23 @@ namespace Microsoft.Build.Evaluation
                        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 { throw new NotImplementedException (); }
+                       get { return raw_imports.Distinct (ResolvedImportComparer.Instance).ToList (); }
                }
 
                public IList<ResolvedImport> ImportsIncludingDuplicates {
@@ -539,8 +621,9 @@ namespace Microsoft.Build.Evaluation
                        get { return new CollectionFromEnumerable<string> (raw_items.Select (i => i.ItemType).Distinct ()); }
                }
 
+               [MonoTODO ("should be different from AllEvaluatedProperties")]
                public ICollection<ProjectProperty> Properties {
-                       get { return properties; }
+                       get { return AllEvaluatedProperties; }
                }
 
                public bool SkipEvaluation { get; set; }
@@ -556,7 +639,6 @@ namespace Microsoft.Build.Evaluation
                
                // 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.
-               Stack<string> files_in_process = new Stack<string> ();
                internal string GetEvaluationTimeThisFileDirectory ()
                {
                        return Path.GetDirectoryName (GetEvaluationTimeThisFile ()) + Path.DirectorySeparatorChar;
@@ -564,7 +646,7 @@ namespace Microsoft.Build.Evaluation
 
                internal string GetEvaluationTimeThisFile ()
                {
-                       return files_in_process.Count > 0 ? files_in_process.Peek () : FullPath;
+                       return ProjectCollection.OngoingImports.Count > 0 ? ProjectCollection.OngoingImports.Peek () : FullPath;
                }
        }
 }
index 7edf867a14976465f9454c4b045e8ca8c9aea043..7b83937c3517e9848290f2a5761cab08726d48c3 100644 (file)
@@ -275,5 +275,11 @@ namespace Microsoft.Build.Evaluation
 
                [MonoTODO]
                public bool IsBuildEnabled { get; set; }
+               
+               Stack<string> ongoing_imports = new Stack<string> ();
+               
+               internal Stack<string> OngoingImports {
+                       get { return ongoing_imports; }
+               }
        }
 }
index 3dd84ad9f42687bc6947de3f7b678f10fca46736..a06f69ec4eb45621f10a03c47a237eabc915f62f 100644 (file)
@@ -115,9 +115,7 @@ namespace Microsoft.Build.Evaluation
                        get { return property_type == PropertyType.Global; }
                }
                public override bool IsImported {
-                       get {
-                               throw new NotImplementedException ();
-                       }
+                       get { return false; }
                }
                public override bool IsReservedProperty {
                        get { return property_type == PropertyType.Reserved; }
@@ -130,19 +128,26 @@ namespace Microsoft.Build.Evaluation
        
        internal class XmlProjectProperty : BaseProjectProperty
        {
-               public XmlProjectProperty (Project project, ProjectPropertyElement xml, PropertyType propertyType)
+               public XmlProjectProperty (Project project, ProjectPropertyElement xml, PropertyType propertyType, bool isImported)
                        : base (project, propertyType, xml.Name)
                {
                        this.xml = xml;
+                       this.is_imported = isImported;
                        UpdateEvaluatedValue ();
                }
                
-               ProjectPropertyElement xml;
+               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; }
                }
index 33657fe37c4a296276fdb9ea53566f84e9a4ddee..e1f7096369e8fbf07fe1e6f3f569a31d11f1bcb7 100644 (file)
@@ -6,6 +6,7 @@ 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
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 (file)
index 0000000..6e95ccb
--- /dev/null
@@ -0,0 +1,102 @@
+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;
+
+namespace MonoTests.Microsoft.Build.Evaluation
+{
+       [TestFixture]
+       public class ResolvedImportTest
+       {
+               [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 ("test_imported.proj"))
+                               ts.Write (imported);
+                       try {
+                               var reader = XmlReader.Create (new StringReader (xml));
+                               var root = ProjectRootElement.Create (reader);
+                               Assert.AreEqual ("test_imported.proj", 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 ("imported.proj");
+                       }
+               }
+
+                       string import_overrides_test_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
+  <PropertyGroup>
+    <A>X</A>
+  </PropertyGroup>
+  <Import Condition=""{0}"" Project='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)
+               {
+                       using (var ts = File.CreateText ("test_imported.proj"))
+                               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");
+                               var b = proj.GetProperty ("B");
+                               Assert.IsNotNull (b, label + "#4");
+                               Assert.AreEqual (valueB, b.EvaluatedValue, label + "#5");
+                               var c = proj.GetProperty ("C");
+                               Assert.IsNotNull (b, label + "#6");
+                               Assert.AreEqual ("c", b.EvaluatedValue, label + "#7");
+                       } finally {
+                               File.Delete ("test_imported.proj");
+                       }
+               }
+
+               [Test]
+               public void ImportAndPropertyOverrides ()
+               {
+                       ImportAndPropertyOverrides ("[1]", "'True'", "a", "Y");
+                       ImportAndPropertyOverrides ("[2]", "A=='X'", "a", "Y"); // evaluated as true
+                       ImportAndPropertyOverrides ("[2]", "B=='Y'", "X", "Y"); // evaluated as false
+                       ImportAndPropertyOverrides ("[2]", "B=='b'", "X", "Y"); // of course not evaluated with imported value
+               }
+       }
+}
+