Add support for ToolsVersion and correctly build msbuild+xbuild assemblies
[mono.git] / mcs / class / Microsoft.Build.Engine / Microsoft.Build.BuildEngine / Project.cs
index d6a43d1eaa485ac93c8885ed20f02c21cfe53ab2..a103b762a177da64651c6379b30f040ec0b987f7 100644 (file)
@@ -60,7 +60,7 @@ namespace Microsoft.Build.BuildEngine {
                bool                            isValidated;
                BuildItemGroupCollection        itemGroups;
                ImportCollection                imports;
-               string[]                        initialTargets;
+               List<string>                    initialTargets;
                Dictionary <string, BuildItemGroup> last_item_group_containing;
                bool                            needToReevaluate;
                Engine                          parentEngine;
@@ -76,6 +76,9 @@ namespace Microsoft.Build.BuildEngine {
                List<string>                    builtTargetKeys;
                bool                            building;
                BuildSettings                   current_settings;
+               Stack<Batch>                    batches;
+               ProjectLoadSettings             project_load_settings;
+
 
                static string extensions_path;
                static XmlNamespaceManager      manager;
@@ -86,9 +89,14 @@ namespace Microsoft.Build.BuildEngine {
                {
                }
 
-               public Project (Engine engine)
+               public Project (Engine engine) : this (engine, null)
+               {
+               }
+               
+               public Project (Engine engine, string toolsVersion)
                {
                        parentEngine  = engine;
+                       ToolsVersion = toolsVersion;
 
                        buildEnabled = ParentEngine.BuildEnabled;
                        xmlDocument = new XmlDocument ();
@@ -99,14 +107,21 @@ namespace Microsoft.Build.BuildEngine {
                        fullFileName = String.Empty;
                        timeOfLastDirty = DateTime.Now;
                        current_settings = BuildSettings.None;
+                       project_load_settings = ProjectLoadSettings.None;
+
+                       encoding = null;
 
                        builtTargetKeys = new List<string> ();
+                       initialTargets = new List<string> ();
+                       defaultTargets = new string [0];
+                       batches = new Stack<Batch> ();
 
                        globalProperties = new BuildPropertyGroup (null, this, null, false);
                        foreach (BuildProperty bp in parentEngine.GlobalProperties)
                                GlobalProperties.AddProperty (bp.Clone (true));
                        
                        ProcessXml ();
+
                }
 
                [MonoTODO ("Not tested")]
@@ -298,7 +313,7 @@ namespace Microsoft.Build.BuildEngine {
                                        return false;
                        }
 
-                       if (!initialTargetsBuilt && initialTargets != null && initialTargets.Length > 0) {
+                       if (!initialTargetsBuilt) {
                                foreach (string target in initialTargets) {
                                        if (!BuildTarget (target.Trim (), targetOutputs))
                                                return false;
@@ -319,8 +334,7 @@ namespace Microsoft.Build.BuildEngine {
                                throw new ArgumentException ("targetNames cannot contain null strings");
 
                        if (!targets.Exists (target_name)) {
-                               //FIXME: Log this!
-                               Console.WriteLine ("Target named '{0}' not found in the project.", target_name);
+                               LogError (fullFileName, "Target named '{0}' not found in the project.", target_name);
                                return false;
                        }
 
@@ -328,9 +342,11 @@ namespace Microsoft.Build.BuildEngine {
                        if (!targets [target_name].Build (key))
                                return false;
 
-                       ITaskItem[] outputs = ParentEngine.BuiltTargetsOutputByName [key];
-                       if (targetOutputs != null)
-                               targetOutputs.Add (target_name, outputs);
+                       ITaskItem[] outputs;
+                       if (ParentEngine.BuiltTargetsOutputByName.TryGetValue (key, out outputs)) {
+                               if (targetOutputs != null)
+                                       targetOutputs.Add (target_name, outputs);
+                       }
                        return true;
                }
 
@@ -415,6 +431,19 @@ namespace Microsoft.Build.BuildEngine {
 
                public void Load (string projectFileName)
                {
+                       Load (projectFileName, ProjectLoadSettings.None);
+               }
+
+               public void Load (string projectFileName, ProjectLoadSettings settings)
+               {
+                       project_load_settings = settings;
+                       if (String.IsNullOrEmpty (projectFileName))
+                               throw new ArgumentNullException ("projectFileName");
+
+                       if (!File.Exists (projectFileName))
+                               throw new ArgumentException (String.Format ("Project file {0} not found", projectFileName),
+                                               "projectFileName");
+
                        this.fullFileName = Utilities.FromMSBuildPath (Path.GetFullPath (projectFileName));
 
                        string filename = fullFileName;
@@ -441,12 +470,24 @@ namespace Microsoft.Build.BuildEngine {
                [MonoTODO ("Not tested")]
                public void Load (TextReader textReader)
                {
+                       Load (textReader, ProjectLoadSettings.None);
+               }
+
+               public void Load (TextReader textReader, ProjectLoadSettings projectLoadSettings)
+               {
+                       project_load_settings = projectLoadSettings;
                        fullFileName = String.Empty;
                        DoLoad (textReader);
                }
 
                public void LoadXml (string projectXml)
                {
+                       LoadXml (projectXml, ProjectLoadSettings.None);
+               }
+
+               public void LoadXml (string projectXml, ProjectLoadSettings projectLoadSettings)
+               {
+                       project_load_settings = projectLoadSettings;
                        fullFileName = String.Empty;
                        DoLoad (new StringReader (projectXml));
                        MarkProjectAsDirty ();
@@ -705,16 +746,7 @@ namespace Microsoft.Build.BuildEngine {
                        try {
                                ParentEngine.RemoveLoadedProject (this);
        
-                               XmlReaderSettings settings = new XmlReaderSettings ();
-       
-                               if (SchemaFile != null) {
-                                       settings.Schemas.Add (null, SchemaFile);
-                                       settings.ValidationType = ValidationType.Schema;
-                                       settings.ValidationEventHandler += new ValidationEventHandler (ValidationCallBack);
-                               }
-       
-                               XmlReader xmlReader = XmlReader.Create (textReader, settings);
-                               xmlDocument.Load (xmlReader);
+                               xmlDocument.Load (textReader);
 
                                if (xmlDocument.DocumentElement.Name == "VisualStudioProject")
                                        throw new InvalidProjectFileException (String.Format (
@@ -722,6 +754,12 @@ namespace Microsoft.Build.BuildEngine {
                                                        "supported by xbuild. You need to convert it to msbuild " +
                                                        "format to build with xbuild.", fullFileName));
 
+                               if (SchemaFile != null) {
+                                       xmlDocument.Schemas.Add (XmlSchema.Read (
+                                                               new StreamReader (SchemaFile), ValidationCallBack));
+                                       xmlDocument.Validate (ValidationCallBack);
+                               }
+
                                if (xmlDocument.DocumentElement.Name != "Project") {
                                        throw new InvalidProjectFileException (String.Format (
                                                "The element <{0}> is unrecognized, or not supported in this context.", xmlDocument.DocumentElement.Name));
@@ -761,15 +799,11 @@ namespace Microsoft.Build.BuildEngine {
                        last_item_group_containing = new Dictionary <string, BuildItemGroup> ();
                        
                        taskDatabase = new TaskDatabase ();
-                       if (ParentEngine.DefaultTasksRegistered)
-                               taskDatabase.CopyTasks (ParentEngine.DefaultTasks);     
+                       taskDatabase.CopyTasks (ParentEngine.GetDefaultTasks (GetToolsVersionToUse ()));
 
-                       if (xmlDocument.DocumentElement.GetAttributeNode ("DefaultTargets") != null)
-                               defaultTargets = xmlDocument.DocumentElement.GetAttribute ("DefaultTargets").Split (';');
-                       else
-                               defaultTargets = new string [0];
-                       
-                       ProcessProjectAttributes (xmlDocument.DocumentElement.Attributes);
+                       initialTargets = new List<string> ();
+                       defaultTargets = new string [0];
+                       PrepareForEvaluate ();
                        ProcessElements (xmlDocument.DocumentElement, null);
                        
                        isDirty = false;
@@ -781,12 +815,17 @@ namespace Microsoft.Build.BuildEngine {
                        foreach (XmlAttribute attr in attributes) {
                                switch (attr.Name) {
                                case "InitialTargets":
-                                       initialTargets = attr.Value.Split (new char [] {';'},
-                                                       StringSplitOptions.RemoveEmptyEntries);
+                                       initialTargets.AddRange (attr.Value.Split (
+                                                                       new char [] {';', ' '},
+                                                                       StringSplitOptions.RemoveEmptyEntries));
                                        break;
                                case "DefaultTargets":
-                                       defaultTargets = attr.Value.Split (new char [] {';'},
+                                       // first non-empty DefaultTargets found is used
+                                       if (defaultTargets == null || defaultTargets.Length == 0)
+                                               defaultTargets = attr.Value.Split (new char [] {';', ' '},
                                                        StringSplitOptions.RemoveEmptyEntries);
+                                       EvaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectDefaultTargets",
+                                                               DefaultTargets, PropertyType.Reserved));
                                        break;
                                }
                        }
@@ -794,6 +833,7 @@ namespace Microsoft.Build.BuildEngine {
 
                internal void ProcessElements (XmlElement rootElement, ImportedProject ip)
                {
+                       ProcessProjectAttributes (rootElement.Attributes);
                        foreach (XmlNode xn in rootElement.ChildNodes) {
                                if (xn is XmlElement) {
                                        XmlElement xe = (XmlElement) xn;
@@ -825,13 +865,13 @@ namespace Microsoft.Build.BuildEngine {
                                                AddChoose (xe);
                                                break;
                                        default:
-                                               throw new InvalidProjectFileException ("Invalid element in project file.");
+                                               throw new InvalidProjectFileException (String.Format ("Invalid element '{0}' in project file.", xe.Name));
                                        }
                                }
                        }
                }
                
-               void Evaluate ()
+               void PrepareForEvaluate ()
                {
                        evaluatedItems = new BuildItemGroup (null, this, null, true);
                        evaluatedItemsIgnoringCondition = new BuildItemGroup (null, this, null, true);
@@ -841,7 +881,10 @@ namespace Microsoft.Build.BuildEngine {
                                RemoveBuiltTargets ();
 
                        InitializeProperties ();
+               }
 
+               void Evaluate ()
+               {
                        groupingCollection.Evaluate ();
 
                        //FIXME: UsingTasks aren't really evaluated. (shouldn't use expressions or anything)
@@ -890,9 +933,15 @@ namespace Microsoft.Build.BuildEngine {
                                                Path.GetFileNameWithoutExtension (fullFileName),
                                                PropertyType.Reserved));
                        EvaluatedProperties.AddProperty (new BuildProperty ("MSBuildBinPath", parentEngine.BinPath, PropertyType.Reserved));
-                       EvaluatedProperties.AddProperty (new BuildProperty ("MSBuildToolsPath", parentEngine.BinPath, PropertyType.Reserved));
+                       string toolsVersionToUse = GetToolsVersionToUse ();
+                       string toolsPath = parentEngine.Toolsets [toolsVersionToUse].ToolsPath;
+                       if (toolsPath == null)
+                               throw new Exception ("Unknown toolsVersion: " + toolsVersionToUse);
+                       EvaluatedProperties.AddProperty (new BuildProperty ("MSBuildToolsPath", toolsPath, PropertyType.Reserved));
+                       EvaluatedProperties.AddProperty (new BuildProperty ("MSBuildToolsVersion", toolsVersionToUse, PropertyType.Reserved));
                        EvaluatedProperties.AddProperty (new BuildProperty ("MSBuildExtensionsPath", ExtensionsPath, PropertyType.Reserved));
                        EvaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectDefaultTargets", DefaultTargets, PropertyType.Reserved));
+                       EvaluatedProperties.AddProperty (new BuildProperty ("OS", OS, PropertyType.Environment));
 
                        // FIXME: make some internal method that will work like GetDirectoryName but output String.Empty on null/String.Empty
                        string projectDir;
@@ -903,6 +952,18 @@ namespace Microsoft.Build.BuildEngine {
 
                        EvaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectDirectory", projectDir, PropertyType.Reserved));
                }
+
+               string GetToolsVersionToUse ()
+               {
+                       if (String.IsNullOrEmpty (ToolsVersion)) {
+                               if (HasToolsVersionAttribute)
+                                       return DefaultToolsVersion;
+                               else
+                                       return parentEngine.DefaultToolsVersion;
+                       } else {
+                               return ToolsVersion;
+                       }
+               }
                
                void AddProjectExtensions (XmlElement xmlElement)
                {
@@ -931,10 +992,32 @@ namespace Microsoft.Build.BuildEngine {
                
                void AddImport (XmlElement xmlElement, ImportedProject importingProject)
                {
-                       Import import;
-                       
-                       import = new Import (xmlElement, this, importingProject);
+                       // eval all the properties etc till the import
+                       groupingCollection.Evaluate (EvaluationType.Property);
+
+                       Import import = new Import (xmlElement, this, importingProject);
+                       if (!ConditionParser.ParseAndEvaluate (import.Condition, this))
+                               return;
+
+                       if (Imports.Contains (import)) {
+                               LogWarning (importingProject != null ? importingProject.FullFileName : fullFileName,
+                                               "A circular reference was found involving the import of {0}. Only" +
+                                               " the first import of this file will be used, ignoring others.",
+                                               import.ProjectPath);
+
+                               return;
+                       }
+
+                       if (String.Compare (fullFileName, import.EvaluatedProjectPath) == 0) {
+                               LogWarning (importingProject != null ? importingProject.FullFileName : fullFileName,
+                                               "The main project file was imported here, which creates a circular " +
+                                               "reference. Ignoring this import.");
+
+                               return;
+                       }
+
                        Imports.Add (import);
+                       import.Evaluate (project_load_settings == ProjectLoadSettings.IgnoreMissingImports);
                }
                
                void AddItemGroup (XmlElement xmlElement, ImportedProject importedProject)
@@ -976,11 +1059,13 @@ namespace Microsoft.Build.BuildEngine {
 
                public string DefaultTargets {
                        get {
-                               return xmlDocument.DocumentElement.GetAttribute ("DefaultTargets");
+                               return String.Join ("; ", defaultTargets);
                        }
                        set {
                                xmlDocument.DocumentElement.SetAttribute ("DefaultTargets", value);
-                               defaultTargets = value.Split (new char [] {';'}, StringSplitOptions.RemoveEmptyEntries);
+                               if (value != null)
+                                       defaultTargets = value.Split (new char [] {';', ' '},
+                                                       StringSplitOptions.RemoveEmptyEntries);
                        }
                }
 
@@ -1030,7 +1115,39 @@ namespace Microsoft.Build.BuildEngine {
                Dictionary<string, BuildItemGroup> perBatchItemsByName;
                Dictionary<string, BuildItemGroup> commonItemsByName;
 
-               internal void SetBatchedItems (Dictionary<string, BuildItemGroup> perBatchItemsByName, Dictionary<string, BuildItemGroup> commonItemsByName)
+               struct Batch {
+                       public Dictionary<string, BuildItemGroup> perBatchItemsByName;
+                       public Dictionary<string, BuildItemGroup> commonItemsByName;
+
+                       public Batch (Dictionary<string, BuildItemGroup> perBatchItemsByName, Dictionary<string, BuildItemGroup> commonItemsByName)
+                       {
+                               this.perBatchItemsByName = perBatchItemsByName;
+                               this.commonItemsByName = commonItemsByName;
+                       }
+               }
+
+               Stack<Batch> Batches {
+                       get { return batches; }
+               }
+
+               internal void PushBatch (Dictionary<string, BuildItemGroup> perBatchItemsByName, Dictionary<string, BuildItemGroup> commonItemsByName)
+               {
+                       batches.Push (new Batch (perBatchItemsByName, commonItemsByName));
+                       SetBatchedItems (perBatchItemsByName, commonItemsByName);
+               }
+
+               internal void PopBatch ()
+               {
+                       batches.Pop ();
+                       if (batches.Count > 0) {
+                               Batch b = batches.Peek ();
+                               SetBatchedItems (b.perBatchItemsByName, b.commonItemsByName);
+                       } else {
+                               SetBatchedItems (null, null);
+                       }
+               }
+
+               void SetBatchedItems (Dictionary<string, BuildItemGroup> perBatchItemsByName, Dictionary<string, BuildItemGroup> commonItemsByName)
                {
                        this.perBatchItemsByName = perBatchItemsByName;
                        this.commonItemsByName = commonItemsByName;
@@ -1039,17 +1156,14 @@ namespace Microsoft.Build.BuildEngine {
                // Honors batching
                internal bool TryGetEvaluatedItemByNameBatched (string itemName, out BuildItemGroup group)
                {
-                       if (perBatchItemsByName == null && commonItemsByName == null)
-                               return EvaluatedItemsByName.TryGetValue (itemName, out group);
+                       if (perBatchItemsByName != null && perBatchItemsByName.TryGetValue (itemName, out group))
+                               return true;
 
-                       if (perBatchItemsByName != null)
-                               return perBatchItemsByName.TryGetValue (itemName, out group);
-
-                       if (commonItemsByName != null)
-                               return commonItemsByName.TryGetValue (itemName, out group);
+                       if (commonItemsByName != null && commonItemsByName.TryGetValue (itemName, out group))
+                               return true;
 
                        group = null;
-                       return false;
+                       return EvaluatedItemsByName.TryGetValue (itemName, out group);
                }
 
                internal string GetMetadataBatched (string itemName, string metadataName)
@@ -1101,7 +1215,7 @@ namespace Microsoft.Build.BuildEngine {
                        return default (T);
                }
 
-               void LogWarning (string filename, string message, params object[] messageArgs)
+               internal void LogWarning (string filename, string message, params object[] messageArgs)
                {
                        BuildWarningEventArgs bwea = new BuildWarningEventArgs (
                                null, null, filename, 0, 0, 0, 0, String.Format (message, messageArgs),
@@ -1109,6 +1223,15 @@ namespace Microsoft.Build.BuildEngine {
                        ParentEngine.EventSource.FireWarningRaised (this, bwea);
                }
 
+               internal void LogError (string filename, string message,
+                                    params object[] messageArgs)
+               {
+                       BuildErrorEventArgs beea = new BuildErrorEventArgs (
+                               null, null, filename, 0, 0, 0, 0, String.Format (message, messageArgs),
+                               null, null);
+                       ParentEngine.EventSource.FireErrorRaised (this, beea);
+               }
+
                static string ExtensionsPath {
                        get {
                                if (extensions_path == null) {
@@ -1152,7 +1275,6 @@ namespace Microsoft.Build.BuildEngine {
                                        throw new InvalidOperationException ("GlobalProperties can not be set to persisted property group.");
                                
                                globalProperties = value;
-                               NeedToReevaluate ();
                        }
                }
 
@@ -1175,11 +1297,14 @@ namespace Microsoft.Build.BuildEngine {
                
                public string InitialTargets {
                        get {
-                               return xmlDocument.DocumentElement.GetAttribute ("InitialTargets");
+                               return String.Join ("; ", initialTargets.ToArray ());
                        }
                        set {
+                               initialTargets.Clear ();
                                xmlDocument.DocumentElement.SetAttribute ("InitialTargets", value);
-                               initialTargets = value.Split (new char [] {';'}, StringSplitOptions.RemoveEmptyEntries);
+                               if (value != null)
+                                       initialTargets.AddRange (value.Split (
+                                                               new char [] {';', ' '}, StringSplitOptions.RemoveEmptyEntries));
                        }
                }
 
@@ -1213,6 +1338,29 @@ namespace Microsoft.Build.BuildEngine {
                        get { return xmlDocument.InnerXml; }
                }
 
+               // corresponds to the xml attribute
+               public string DefaultToolsVersion {
+                       get {
+                               if (xmlDocument != null)
+                                       return xmlDocument.DocumentElement.GetAttribute ("ToolsVersion");
+                               return null;
+                       }
+                       set {
+                               if (xmlDocument != null)
+                                       xmlDocument.DocumentElement.SetAttribute ("ToolsVersion", value);
+                       }
+               }
+
+               public bool HasToolsVersionAttribute {
+                       get {
+                               return xmlDocument != null && xmlDocument.DocumentElement.HasAttribute ("ToolsVersion");
+                       }
+               }
+               
+               public string ToolsVersion {
+                       get; internal set;
+               }
+
                internal List<string> BuiltTargetKeys {
                        get { return builtTargetKeys; }
                }
@@ -1243,6 +1391,22 @@ namespace Microsoft.Build.BuildEngine {
                internal static string XmlNamespace {
                        get { return ns; }
                }
+
+               static string OS {
+                       get {
+                               PlatformID pid = Environment.OSVersion.Platform;
+                               switch ((int)pid) {
+                               case 128:
+                               case 4:
+                                       return "Unix";
+                               case 6:
+                                       return "OSX";
+                               default:
+                                       return "Windows_NT";
+                               }
+                       }
+               }
+
        }
 }