2 // Project.cs: Project class
5 // Marek Sieradzki (marek.sieradzki@gmail.com)
6 // Ankit Jain (jankit@novell.com)
8 // (C) 2005 Marek Sieradzki
9 // Copyright 2011 Novell, Inc (http://www.novell.com)
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 using System.Collections;
34 using System.Collections.Generic;
35 using System.Collections.Specialized;
38 using System.Reflection;
41 using System.Xml.Schema;
42 using Microsoft.Build.Framework;
43 using Mono.XBuild.Framework;
44 using Mono.XBuild.CommandLine;
46 namespace Microsoft.Build.BuildEngine {
47 public class Project {
50 Dictionary <string, List <string>> conditionedProperties;
51 string[] defaultTargets;
53 BuildItemGroup evaluatedItems;
54 BuildItemGroup evaluatedItemsIgnoringCondition;
55 Dictionary <string, BuildItemGroup> evaluatedItemsByName;
56 Dictionary <string, BuildItemGroup> evaluatedItemsByNameIgnoringCondition;
57 BuildPropertyGroup evaluatedProperties;
58 string firstTargetName;
60 BuildPropertyGroup globalProperties;
61 GroupingCollection groupingCollection;
64 BuildItemGroupCollection itemGroups;
65 ImportCollection imports;
66 List<string> initialTargets;
67 Dictionary <string, BuildItemGroup> last_item_group_containing;
68 bool needToReevaluate;
70 BuildPropertyGroupCollection propertyGroups;
72 TaskDatabase taskDatabase;
73 TargetCollection targets;
74 DateTime timeOfLastDirty;
75 UsingTaskCollection usingTasks;
76 XmlDocument xmlDocument;
78 bool initialTargetsBuilt;
80 BuildSettings current_settings;
83 // This is used to keep track of "current" file,
84 // which is then used to set the reserved properties
85 // $(MSBuildThisFile*)
86 Stack<string> this_file_property_stack;
87 ProjectLoadSettings project_load_settings;
90 static string extensions_path;
91 static XmlNamespaceManager manager;
92 static string ns = "http://schemas.microsoft.com/developer/msbuild/2003";
95 : this (Engine.GlobalEngine)
99 public Project (Engine engine) : this (engine, null)
103 public Project (Engine engine, string toolsVersion)
105 parentEngine = engine;
106 ToolsVersion = toolsVersion;
108 buildEnabled = ParentEngine.BuildEnabled;
109 xmlDocument = new XmlDocument ();
110 xmlDocument.PreserveWhitespace = false;
111 xmlDocument.AppendChild (xmlDocument.CreateElement ("Project", XmlNamespace));
112 xmlDocument.DocumentElement.SetAttribute ("xmlns", ns);
114 fullFileName = String.Empty;
115 timeOfLastDirty = DateTime.Now;
116 current_settings = BuildSettings.None;
117 project_load_settings = ProjectLoadSettings.None;
121 initialTargets = new List<string> ();
122 defaultTargets = new string [0];
123 batches = new Stack<Batch> ();
124 this_file_property_stack = new Stack<string> ();
126 globalProperties = new BuildPropertyGroup (null, this, null, false);
127 foreach (BuildProperty bp in parentEngine.GlobalProperties)
128 GlobalProperties.AddProperty (bp.Clone (true));
134 [MonoTODO ("Not tested")]
135 public void AddNewImport (string importLocation,
136 string importCondition)
138 if (importLocation == null)
139 throw new ArgumentNullException ("importLocation");
141 XmlElement importElement = xmlDocument.CreateElement ("Import", XmlNamespace);
142 xmlDocument.DocumentElement.AppendChild (importElement);
143 importElement.SetAttribute ("Project", importLocation);
144 if (!String.IsNullOrEmpty (importCondition))
145 importElement.SetAttribute ("Condition", importCondition);
147 AddImport (importElement, null, false);
148 MarkProjectAsDirty ();
152 public BuildItem AddNewItem (string itemName,
155 return AddNewItem (itemName, itemInclude, false);
158 [MonoTODO ("Adds item not in the same place as MS")]
159 public BuildItem AddNewItem (string itemName,
161 bool treatItemIncludeAsLiteral)
165 if (itemGroups.Count == 0)
166 big = AddNewItemGroup ();
168 if (last_item_group_containing.ContainsKey (itemName)) {
169 big = last_item_group_containing [itemName];
172 BuildItemGroup [] groups = new BuildItemGroup [itemGroups.Count];
173 itemGroups.CopyTo (groups, 0);
178 BuildItem item = big.AddNewItem (itemName, itemInclude, treatItemIncludeAsLiteral);
180 MarkProjectAsDirty ();
186 [MonoTODO ("Not tested")]
187 public BuildItemGroup AddNewItemGroup ()
189 XmlElement element = xmlDocument.CreateElement ("ItemGroup", XmlNamespace);
190 xmlDocument.DocumentElement.AppendChild (element);
192 BuildItemGroup big = new BuildItemGroup (element, this, null, false);
193 itemGroups.Add (big);
194 MarkProjectAsDirty ();
200 [MonoTODO ("Ignores insertAtEndOfProject")]
201 public BuildPropertyGroup AddNewPropertyGroup (bool insertAtEndOfProject)
203 XmlElement element = xmlDocument.CreateElement ("PropertyGroup", XmlNamespace);
204 xmlDocument.DocumentElement.AppendChild (element);
206 BuildPropertyGroup bpg = new BuildPropertyGroup (element, this, null, false);
207 propertyGroups.Add (bpg);
208 MarkProjectAsDirty ();
214 [MonoTODO ("Not tested, isn't added to TaskDatabase (no reevaluation)")]
215 public void AddNewUsingTaskFromAssemblyFile (string taskName,
218 if (taskName == null)
219 throw new ArgumentNullException ("taskName");
220 if (assemblyFile == null)
221 throw new ArgumentNullException ("assemblyFile");
223 XmlElement element = xmlDocument.CreateElement ("UsingTask", XmlNamespace);
224 xmlDocument.DocumentElement.AppendChild (element);
225 element.SetAttribute ("TaskName", taskName);
226 element.SetAttribute ("AssemblyFile", assemblyFile);
228 UsingTask ut = new UsingTask (element, this, null);
230 MarkProjectAsDirty ();
233 [MonoTODO ("Not tested, isn't added to TaskDatabase (no reevaluation)")]
234 public void AddNewUsingTaskFromAssemblyName (string taskName,
237 if (taskName == null)
238 throw new ArgumentNullException ("taskName");
239 if (assemblyName == null)
240 throw new ArgumentNullException ("assemblyName");
242 XmlElement element = xmlDocument.CreateElement ("UsingTask", XmlNamespace);
243 xmlDocument.DocumentElement.AppendChild (element);
244 element.SetAttribute ("TaskName", taskName);
245 element.SetAttribute ("AssemblyName", assemblyName);
247 UsingTask ut = new UsingTask (element, this, null);
249 MarkProjectAsDirty ();
252 [MonoTODO ("Not tested")]
255 return Build (new string [0]);
258 [MonoTODO ("Not tested")]
259 public bool Build (string targetName)
261 if (targetName == null)
262 return Build ((string[]) null);
264 return Build (new string [1] { targetName });
267 [MonoTODO ("Not tested")]
268 public bool Build (string [] targetNames)
270 return Build (targetNames, null);
273 [MonoTODO ("Not tested")]
274 public bool Build (string [] targetNames,
275 IDictionary targetOutputs)
277 return Build (targetNames, targetOutputs, BuildSettings.None);
280 [MonoTODO ("Not tested")]
281 public bool Build (string [] targetNames,
282 IDictionary targetOutputs,
283 BuildSettings buildFlags)
287 ParentEngine.StartProjectBuild (this, targetNames);
289 // Invoking this to emit a warning in case of unsupported
291 GetToolsVersionToUse (true);
293 string current_directory = Environment.CurrentDirectory;
295 current_settings = buildFlags;
296 if (!String.IsNullOrEmpty (fullFileName))
297 Directory.SetCurrentDirectory (Path.GetDirectoryName (fullFileName));
299 result = BuildInternal (targetNames, targetOutputs, buildFlags);
301 ParentEngine.EndProjectBuild (this, result);
302 current_settings = BuildSettings.None;
303 Directory.SetCurrentDirectory (current_directory);
310 bool BuildInternal (string [] targetNames,
311 IDictionary targetOutputs,
312 BuildSettings buildFlags)
315 if (buildFlags == BuildSettings.None) {
316 needToReevaluate = false;
321 ProcessBeforeAndAfterTargets ();
324 if (targetNames == null || targetNames.Length == 0) {
325 if (defaultTargets != null && defaultTargets.Length != 0) {
326 targetNames = defaultTargets;
327 } else if (firstTargetName != null) {
328 targetNames = new string [1] { firstTargetName};
330 if (targets == null || targets.Count == 0) {
331 LogError (fullFileName, "No target found in the project");
339 if (!initialTargetsBuilt) {
340 foreach (string target in initialTargets) {
341 if (!BuildTarget (target.Trim (), targetOutputs))
344 initialTargetsBuilt = true;
347 foreach (string target in targetNames)
348 if (!BuildTarget (target.Trim (), targetOutputs))
354 bool BuildTarget (string target_name, IDictionary targetOutputs)
356 if (target_name == null)
357 throw new ArgumentException ("targetNames cannot contain null strings");
359 if (!targets.Exists (target_name)) {
360 LogError (fullFileName, "Target named '{0}' not found in the project.", target_name);
364 string key = GetKeyForTarget (target_name);
365 if (!targets [target_name].Build (key))
369 if (ParentEngine.BuiltTargetsOutputByName.TryGetValue (key, out outputs)) {
370 if (targetOutputs != null)
371 targetOutputs.Add (target_name, outputs);
376 internal string GetKeyForTarget (string target_name)
378 return GetKeyForTarget (target_name, true);
381 internal string GetKeyForTarget (string target_name, bool include_global_properties)
383 // target name is case insensitive
384 return fullFileName + ":" + target_name.ToLower () +
385 (include_global_properties ? (":" + GlobalPropertiesToString (GlobalProperties))
389 string GlobalPropertiesToString (BuildPropertyGroup bgp)
391 StringBuilder sb = new StringBuilder ();
392 foreach (BuildProperty bp in bgp)
393 sb.AppendFormat (" {0}:{1}", bp.Name, bp.FinalValue);
394 return sb.ToString ();
398 void ProcessBeforeAndAfterTargets ()
400 var beforeTable = Targets.AsIEnumerable ()
401 .SelectMany (target => GetTargetNamesFromString (target.BeforeTargets),
402 (target, before_target) => new {before_target, name = target.Name})
403 .ToLookup (x => x.before_target, x => x.name)
404 .ToDictionary (x => x.Key, x => x.Distinct ().ToList ());
406 foreach (var pair in beforeTable) {
407 if (targets.Exists (pair.Key))
408 targets [pair.Key].BeforeThisTargets = pair.Value;
410 LogWarning (FullFileName, "Target '{0}', not found in the project", pair.Key);
413 var afterTable = Targets.AsIEnumerable ()
414 .SelectMany (target => GetTargetNamesFromString (target.AfterTargets),
415 (target, after_target) => new {after_target, name = target.Name})
416 .ToLookup (x => x.after_target, x => x.name)
417 .ToDictionary (x => x.Key, x => x.Distinct ().ToList ());
419 foreach (var pair in afterTable) {
420 if (targets.Exists (pair.Key))
421 targets [pair.Key].AfterThisTargets = pair.Value;
423 LogWarning (FullFileName, "Target '{0}', not found in the project", pair.Key);
427 string[] GetTargetNamesFromString (string targets)
429 Expression expr = new Expression ();
430 expr.Parse (targets, ParseOptions.AllowItemsNoMetadataAndSplit);
431 return (string []) expr.ConvertTo (this, typeof (string []));
436 public string [] GetConditionedPropertyValues (string propertyName)
438 if (conditionedProperties.ContainsKey (propertyName))
439 return conditionedProperties [propertyName].ToArray ();
441 return new string [0];
444 public BuildItemGroup GetEvaluatedItemsByName (string itemName)
446 if (needToReevaluate) {
447 needToReevaluate = false;
451 if (evaluatedItemsByName.ContainsKey (itemName))
452 return evaluatedItemsByName [itemName];
454 return new BuildItemGroup (this);
457 public BuildItemGroup GetEvaluatedItemsByNameIgnoringCondition (string itemName)
459 if (needToReevaluate) {
460 needToReevaluate = false;
464 if (evaluatedItemsByNameIgnoringCondition.ContainsKey (itemName))
465 return evaluatedItemsByNameIgnoringCondition [itemName];
467 return new BuildItemGroup (this);
470 public string GetEvaluatedProperty (string propertyName)
472 if (needToReevaluate) {
473 needToReevaluate = false;
477 if (propertyName == null)
478 throw new ArgumentNullException ("propertyName");
480 BuildProperty bp = evaluatedProperties [propertyName];
482 return bp == null ? null : (string) bp;
485 [MonoTODO ("We should remember that node and not use XPath to get it")]
486 public string GetProjectExtensions (string id)
488 if (id == null || id == String.Empty)
491 XmlNode node = xmlDocument.SelectSingleNode (String.Format ("/tns:Project/tns:ProjectExtensions/tns:{0}", id), XmlNamespaceManager);
496 return node.InnerXml;
500 public void Load (string projectFileName)
502 Load (projectFileName, ProjectLoadSettings.None);
505 public void Load (string projectFileName, ProjectLoadSettings settings)
507 project_load_settings = settings;
508 if (String.IsNullOrEmpty (projectFileName))
509 throw new ArgumentNullException ("projectFileName");
511 if (!File.Exists (projectFileName))
512 throw new ArgumentException (String.Format ("Project file {0} not found", projectFileName),
515 this.fullFileName = Utilities.FromMSBuildPath (Path.GetFullPath (projectFileName));
516 PushThisFileProperty (fullFileName);
518 string filename = fullFileName;
519 if (String.Compare (Path.GetExtension (fullFileName), ".sln", true) == 0) {
520 Project tmp_project = ParentEngine.CreateNewProject ();
521 tmp_project.FullFileName = filename;
522 SolutionParser sln_parser = new SolutionParser ();
523 sln_parser.ParseSolution (fullFileName, tmp_project, delegate (int errorNumber, string message) {
524 LogWarning (filename, message);
526 filename = fullFileName + ".proj";
528 tmp_project.Save (filename);
529 ParentEngine.RemoveLoadedProject (tmp_project);
530 DoLoad (new StreamReader (filename));
532 if (Environment.GetEnvironmentVariable ("XBUILD_EMIT_SOLUTION") == null)
533 File.Delete (filename);
536 DoLoad (new StreamReader (filename));
540 [MonoTODO ("Not tested")]
541 public void Load (TextReader textReader)
543 Load (textReader, ProjectLoadSettings.None);
546 public void Load (TextReader textReader, ProjectLoadSettings projectLoadSettings)
548 project_load_settings = projectLoadSettings;
549 fullFileName = String.Empty;
553 public void LoadXml (string projectXml)
555 LoadXml (projectXml, ProjectLoadSettings.None);
558 public void LoadXml (string projectXml, ProjectLoadSettings projectLoadSettings)
560 project_load_settings = projectLoadSettings;
561 fullFileName = String.Empty;
562 DoLoad (new StringReader (projectXml));
563 MarkProjectAsDirty ();
567 public void MarkProjectAsDirty ()
570 timeOfLastDirty = DateTime.Now;
573 [MonoTODO ("Not tested")]
574 public void RemoveAllItemGroups ()
576 int length = ItemGroups.Count;
577 BuildItemGroup [] groups = new BuildItemGroup [length];
578 ItemGroups.CopyTo (groups, 0);
580 for (int i = 0; i < length; i++)
581 RemoveItemGroup (groups [i]);
583 MarkProjectAsDirty ();
587 [MonoTODO ("Not tested")]
588 public void RemoveAllPropertyGroups ()
590 int length = PropertyGroups.Count;
591 BuildPropertyGroup [] groups = new BuildPropertyGroup [length];
592 PropertyGroups.CopyTo (groups, 0);
594 for (int i = 0; i < length; i++)
595 RemovePropertyGroup (groups [i]);
597 MarkProjectAsDirty ();
602 public void RemoveItem (BuildItem itemToRemove)
604 if (itemToRemove == null)
605 throw new ArgumentNullException ("itemToRemove");
607 if (!itemToRemove.FromXml && !itemToRemove.HasParentItem)
608 throw new InvalidOperationException ("The object passed in is not part of the project.");
610 BuildItemGroup big = itemToRemove.ParentItemGroup;
612 if (big.Count == 1) {
613 // ParentItemGroup for items from xml and that have parent is the same
614 groupingCollection.Remove (big);
616 if (big.ParentProject != this)
617 throw new InvalidOperationException ("The object passed in is not part of the project.");
619 if (itemToRemove.FromXml)
620 big.RemoveItem (itemToRemove);
622 big.RemoveItem (itemToRemove.ParentItem);
625 MarkProjectAsDirty ();
629 [MonoTODO ("Not tested")]
630 public void RemoveItemGroup (BuildItemGroup itemGroupToRemove)
632 if (itemGroupToRemove == null)
633 throw new ArgumentNullException ("itemGroupToRemove");
635 groupingCollection.Remove (itemGroupToRemove);
636 MarkProjectAsDirty ();
640 // NOTE: does not modify imported projects
641 public void RemoveItemGroupsWithMatchingCondition (string matchingCondition)
643 throw new NotImplementedException ();
647 public void RemoveItemsByName (string itemName)
649 if (itemName == null)
650 throw new ArgumentNullException ("itemName");
652 throw new NotImplementedException ();
655 [MonoTODO ("Not tested")]
656 public void RemovePropertyGroup (BuildPropertyGroup propertyGroupToRemove)
658 if (propertyGroupToRemove == null)
659 throw new ArgumentNullException ("propertyGroupToRemove");
661 groupingCollection.Remove (propertyGroupToRemove);
662 MarkProjectAsDirty ();
666 // NOTE: does not modify imported projects
667 public void RemovePropertyGroupsWithMatchingCondition (string matchCondition)
669 throw new NotImplementedException ();
673 public void ResetBuildStatus ()
675 // hack to allow built targets to be removed
681 public void Save (string projectFileName)
683 Save (projectFileName, Encoding.Default);
687 [MonoTODO ("Ignores encoding")]
688 public void Save (string projectFileName, Encoding encoding)
690 xmlDocument.Save (projectFileName);
694 public void Save (TextWriter outTextWriter)
696 xmlDocument.Save (outTextWriter);
700 public void SetImportedProperty (string propertyName,
701 string propertyValue,
703 Project importProject)
705 SetImportedProperty (propertyName, propertyValue, condition, importProject,
706 PropertyPosition.UseExistingOrCreateAfterLastPropertyGroup);
709 public void SetImportedProperty (string propertyName,
710 string propertyValue,
712 Project importedProject,
713 PropertyPosition position)
715 SetImportedProperty (propertyName, propertyValue, condition, importedProject,
716 PropertyPosition.UseExistingOrCreateAfterLastPropertyGroup, false);
720 public void SetImportedProperty (string propertyName,
721 string propertyValue,
723 Project importedProject,
724 PropertyPosition position,
725 bool treatPropertyValueAsLiteral)
727 throw new NotImplementedException ();
730 public void SetProjectExtensions (string id, string xmlText)
733 throw new ArgumentNullException ("id");
735 throw new ArgumentNullException ("xmlText");
737 XmlNode projectExtensions, node;
739 projectExtensions = xmlDocument.SelectSingleNode ("/tns:Project/tns:ProjectExtensions", XmlNamespaceManager);
741 if (projectExtensions == null) {
742 projectExtensions = xmlDocument.CreateElement ("ProjectExtensions", XmlNamespace);
743 xmlDocument.DocumentElement.AppendChild (projectExtensions);
745 node = xmlDocument.CreateElement (id, XmlNamespace);
746 node.InnerXml = xmlText;
747 projectExtensions.AppendChild (node);
749 node = xmlDocument.SelectSingleNode (String.Format ("/tns:Project/tns:ProjectExtensions/tns:{0}", id), XmlNamespaceManager);
752 node = xmlDocument.CreateElement (id, XmlNamespace);
753 projectExtensions.AppendChild (node);
756 node.InnerXml = xmlText;
760 MarkProjectAsDirty ();
763 public void SetProperty (string propertyName,
764 string propertyValue)
766 SetProperty (propertyName, propertyValue, "true",
767 PropertyPosition.UseExistingOrCreateAfterLastPropertyGroup, false);
770 public void SetProperty (string propertyName,
771 string propertyValue,
774 SetProperty (propertyName, propertyValue, condition,
775 PropertyPosition.UseExistingOrCreateAfterLastPropertyGroup);
778 public void SetProperty (string propertyName,
779 string propertyValue,
781 PropertyPosition position)
783 SetProperty (propertyName, propertyValue, condition,
784 PropertyPosition.UseExistingOrCreateAfterLastPropertyGroup, false);
788 public void SetProperty (string propertyName,
789 string propertyValue,
791 PropertyPosition position,
792 bool treatPropertyValueAsLiteral)
794 throw new NotImplementedException ();
797 internal void Unload ()
802 internal void CheckUnloaded ()
805 throw new InvalidOperationException ("This project object has been unloaded from the MSBuild engine and is no longer valid.");
808 internal void NeedToReevaluate ()
810 needToReevaluate = true;
813 // Does the actual loading.
814 void DoLoad (TextReader textReader)
817 ParentEngine.RemoveLoadedProject (this);
819 xmlDocument.Load (textReader);
821 if (xmlDocument.DocumentElement.Name == "VisualStudioProject")
822 throw new InvalidProjectFileException (String.Format (
823 "Project file '{0}' is a VS2003 project, which is not " +
824 "supported by xbuild. You need to convert it to msbuild " +
825 "format to build with xbuild.", fullFileName));
827 if (SchemaFile != null) {
828 xmlDocument.Schemas.Add (XmlSchema.Read (
829 new StreamReader (SchemaFile), ValidationCallBack));
830 xmlDocument.Validate (ValidationCallBack);
833 if (xmlDocument.DocumentElement.Name != "Project") {
834 throw new InvalidProjectFileException (String.Format (
835 "The element <{0}> is unrecognized, or not supported in this context.", xmlDocument.DocumentElement.Name));
838 if (xmlDocument.DocumentElement.GetAttribute ("xmlns") != ns) {
839 throw new InvalidProjectFileException (
840 @"The default XML namespace of the project must be the MSBuild XML namespace." +
841 " If the project is authored in the MSBuild 2003 format, please add " +
842 "xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\" to the <Project> element. " +
843 "If the project has been authored in the old 1.0 or 1.2 format, please convert it to MSBuild 2003 format. ");
846 ParentEngine.AddLoadedProject (this);
847 } catch (Exception e) {
848 throw new InvalidProjectFileException (String.Format ("{0}: {1}",
849 fullFileName, e.Message), e);
851 if (textReader != null)
863 groupingCollection = new GroupingCollection (this);
864 imports = new ImportCollection (groupingCollection);
865 usingTasks = new UsingTaskCollection (this);
866 itemGroups = new BuildItemGroupCollection (groupingCollection);
867 propertyGroups = new BuildPropertyGroupCollection (groupingCollection);
868 targets = new TargetCollection (this);
869 last_item_group_containing = new Dictionary <string, BuildItemGroup> ();
871 string effective_tools_version = GetToolsVersionToUse (false);
872 taskDatabase = new TaskDatabase ();
873 taskDatabase.CopyTasks (ParentEngine.GetDefaultTasks (effective_tools_version));
875 initialTargets = new List<string> ();
876 defaultTargets = new string [0];
877 PrepareForEvaluate (effective_tools_version);
878 ProcessElements (xmlDocument.DocumentElement, null);
884 void ProcessProjectAttributes (XmlAttributeCollection attributes)
886 foreach (XmlAttribute attr in attributes) {
888 case "InitialTargets":
889 initialTargets.AddRange (attr.Value.Split (
890 new char [] {';', ' '},
891 StringSplitOptions.RemoveEmptyEntries));
893 case "DefaultTargets":
894 // first non-empty DefaultTargets found is used
895 if (defaultTargets == null || defaultTargets.Length == 0)
896 defaultTargets = attr.Value.Split (new char [] {';', ' '},
897 StringSplitOptions.RemoveEmptyEntries);
898 EvaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectDefaultTargets",
899 DefaultTargets, PropertyType.Reserved));
905 internal void ProcessElements (XmlElement rootElement, ImportedProject ip)
907 ProcessProjectAttributes (rootElement.Attributes);
908 foreach (XmlNode xn in rootElement.ChildNodes) {
909 if (xn is XmlElement) {
910 XmlElement xe = (XmlElement) xn;
912 case "ProjectExtensions":
913 AddProjectExtensions (xe);
924 AddUsingTask (xe, ip);
927 AddImport (xe, ip, true);
930 AddItemGroup (xe, ip);
932 case "PropertyGroup":
933 AddPropertyGroup (xe, ip);
939 throw new InvalidProjectFileException (String.Format ("Invalid element '{0}' in project file.", xe.Name));
945 void PrepareForEvaluate (string effective_tools_version)
947 evaluatedItems = new BuildItemGroup (null, this, null, true);
948 evaluatedItemsIgnoringCondition = new BuildItemGroup (null, this, null, true);
949 evaluatedItemsByName = new Dictionary <string, BuildItemGroup> (StringComparer.InvariantCultureIgnoreCase);
950 evaluatedItemsByNameIgnoringCondition = new Dictionary <string, BuildItemGroup> (StringComparer.InvariantCultureIgnoreCase);
951 if (building && current_settings == BuildSettings.None)
952 RemoveBuiltTargets ();
954 InitializeProperties (effective_tools_version);
959 groupingCollection.Evaluate ();
961 //FIXME: UsingTasks aren't really evaluated. (shouldn't use expressions or anything)
962 foreach (UsingTask usingTask in UsingTasks)
963 usingTask.Evaluate ();
966 // Removes entries of all earlier built targets for this project
967 void RemoveBuiltTargets ()
969 ParentEngine.ClearBuiltTargetsForProject (this);
972 void InitializeProperties (string effective_tools_version)
976 evaluatedProperties = new BuildPropertyGroup (null, null, null, true);
977 conditionedProperties = new Dictionary<string, List<string>> ();
979 foreach (BuildProperty gp in GlobalProperties) {
980 bp = new BuildProperty (gp.Name, gp.Value, PropertyType.Global);
981 evaluatedProperties.AddProperty (bp);
984 foreach (BuildProperty gp in GlobalProperties)
985 ParentEngine.GlobalProperties.AddProperty (gp);
987 // add properties that we dont have from parent engine's
989 foreach (BuildProperty gp in ParentEngine.GlobalProperties) {
990 if (evaluatedProperties [gp.Name] == null) {
991 bp = new BuildProperty (gp.Name, gp.Value, PropertyType.Global);
992 evaluatedProperties.AddProperty (bp);
996 foreach (DictionaryEntry de in Environment.GetEnvironmentVariables ()) {
997 bp = new BuildProperty ((string) de.Key, (string) de.Value, PropertyType.Environment);
998 evaluatedProperties.AddProperty (bp);
1001 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectFile", Path.GetFileName (fullFileName),
1002 PropertyType.Reserved));
1003 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectFullPath", fullFileName, PropertyType.Reserved));
1004 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectName",
1005 Path.GetFileNameWithoutExtension (fullFileName),
1006 PropertyType.Reserved));
1007 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectExtension",
1008 Path.GetExtension (fullFileName),
1009 PropertyType.Reserved));
1011 string toolsPath = parentEngine.Toolsets [effective_tools_version].ToolsPath;
1012 if (toolsPath == null)
1013 throw new Exception (String.Format ("Invalid tools version '{0}', no tools path set for this.", effective_tools_version));
1014 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildBinPath", toolsPath, PropertyType.Reserved));
1015 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildToolsPath", toolsPath, PropertyType.Reserved));
1016 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildToolsRoot", Path.GetDirectoryName (toolsPath), PropertyType.Reserved));
1017 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildToolsVersion", effective_tools_version, PropertyType.Reserved));
1018 SetExtensionsPathProperties (DefaultExtensionsPath);
1019 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectDefaultTargets", DefaultTargets, PropertyType.Reserved));
1020 evaluatedProperties.AddProperty (new BuildProperty ("OS", OS, PropertyType.Environment));
1022 // FIXME: make some internal method that will work like GetDirectoryName but output String.Empty on null/String.Empty
1024 if (FullFileName == String.Empty)
1025 projectDir = Environment.CurrentDirectory;
1027 projectDir = Path.GetDirectoryName (FullFileName);
1029 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectDirectory", projectDir, PropertyType.Reserved));
1031 if (this_file_property_stack.Count > 0)
1032 // Just re-inited the properties, but according to the stack,
1033 // we should have a MSBuild*This* property set
1034 SetMSBuildThisFileProperties (this_file_property_stack.Peek ());
1037 internal void SetExtensionsPathProperties (string extn_path)
1039 if (!String.IsNullOrEmpty (extn_path)) {
1040 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildExtensionsPath", extn_path, PropertyType.Reserved));
1041 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildExtensionsPath32", extn_path, PropertyType.Reserved));
1042 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildExtensionsPath64", extn_path, PropertyType.Reserved));
1047 // ToolsVersion property
1048 // ToolsVersion attribute on the project
1049 // parentEngine's DefaultToolsVersion
1050 string GetToolsVersionToUse (bool emitWarning)
1052 if (!String.IsNullOrEmpty (ToolsVersion))
1053 return ToolsVersion;
1055 if (!HasToolsVersionAttribute)
1056 return parentEngine.DefaultToolsVersion;
1058 if (parentEngine.Toolsets [DefaultToolsVersion] == null) {
1060 LogWarning (FullFileName, "Project has unknown ToolsVersion '{0}'. Using the default tools version '{1}' instead.",
1061 DefaultToolsVersion, parentEngine.DefaultToolsVersion);
1062 return parentEngine.DefaultToolsVersion;
1065 return DefaultToolsVersion;
1068 void AddProjectExtensions (XmlElement xmlElement)
1072 void AddMessage (XmlElement xmlElement)
1076 void AddTarget (XmlElement xmlElement, ImportedProject importedProject)
1078 Target target = new Target (xmlElement, this, importedProject);
1079 targets.AddTarget (target);
1081 if (firstTargetName == null)
1082 firstTargetName = target.Name;
1085 void AddUsingTask (XmlElement xmlElement, ImportedProject importedProject)
1087 UsingTask usingTask;
1089 usingTask = new UsingTask (xmlElement, this, importedProject);
1090 UsingTasks.Add (usingTask);
1093 void AddImport (XmlElement xmlElement, ImportedProject importingProject, bool evaluate_properties)
1095 // eval all the properties etc till the import
1096 if (evaluate_properties)
1097 groupingCollection.Evaluate (EvaluationType.Property);
1100 PushThisFileProperty (importingProject != null ? importingProject.FullFileName : FullFileName);
1102 string project_attribute = xmlElement.GetAttribute ("Project");
1103 if (String.IsNullOrEmpty (project_attribute))
1104 throw new InvalidProjectFileException ("The required attribute \"Project\" is missing from element <Import>.");
1106 Import.ForEachExtensionPathTillFound (xmlElement, this, importingProject,
1107 (importPath, from_source_msg) => AddSingleImport (xmlElement, importPath, importingProject, from_source_msg));
1109 PopThisFileProperty ();
1113 bool AddSingleImport (XmlElement xmlElement, string projectPath, ImportedProject importingProject, string from_source_msg)
1115 Import import = new Import (xmlElement, projectPath, this, importingProject);
1116 if (!ConditionParser.ParseAndEvaluate (import.Condition, this)) {
1117 ParentEngine.LogMessage (MessageImportance.Low,
1118 "Not importing project '{0}' as the condition '{1}' is false",
1119 import.ProjectPath, import.Condition);
1123 Import existingImport;
1124 if (Imports.TryGetImport (import, out existingImport)) {
1125 if (importingProject == null)
1126 LogWarning (fullFileName,
1127 "Cannot import project '{0}' again. It was already imported by " +
1129 projectPath, existingImport.ContainedInProjectFileName);
1131 LogWarning (importingProject != null ? importingProject.FullFileName : fullFileName,
1132 "A circular reference was found involving the import of '{0}'. " +
1133 "It was earlier imported by '{1}'. Only " +
1134 "the first import of this file will be used, ignoring others.",
1135 import.EvaluatedProjectPath, existingImport.ContainedInProjectFileName);
1140 if (String.Compare (fullFileName, import.EvaluatedProjectPath) == 0) {
1141 LogWarning (importingProject != null ? importingProject.FullFileName : fullFileName,
1142 "The main project file was imported here, which creates a circular " +
1143 "reference. Ignoring this import.");
1148 Imports.Add (import);
1149 string importingFile = importingProject != null ? importingProject.FullFileName : FullFileName;
1150 ParentEngine.LogMessage (MessageImportance.Low,
1151 "{0}: Importing project {1} {2}",
1152 importingFile, import.EvaluatedProjectPath, from_source_msg);
1154 import.Evaluate (project_load_settings == ProjectLoadSettings.IgnoreMissingImports);
1158 void AddItemGroup (XmlElement xmlElement, ImportedProject importedProject)
1160 BuildItemGroup big = new BuildItemGroup (xmlElement, this, importedProject, false);
1161 ItemGroups.Add (big);
1164 void AddPropertyGroup (XmlElement xmlElement, ImportedProject importedProject)
1166 BuildPropertyGroup bpg = new BuildPropertyGroup (xmlElement, this, importedProject, false);
1167 PropertyGroups.Add (bpg);
1170 void AddChoose (XmlElement xmlElement, ImportedProject importedProject)
1172 BuildChoose bc = new BuildChoose (xmlElement, this, importedProject);
1173 groupingCollection.Add (bc);
1176 static void ValidationCallBack (object sender, ValidationEventArgs e)
1178 Console.WriteLine ("Validation Error: {0}", e.Message);
1181 public bool BuildEnabled {
1183 return buildEnabled;
1186 buildEnabled = value;
1191 public Encoding Encoding {
1192 get { return encoding; }
1195 public string DefaultTargets {
1197 return String.Join ("; ", defaultTargets);
1200 xmlDocument.DocumentElement.SetAttribute ("DefaultTargets", value);
1202 defaultTargets = value.Split (new char [] {';', ' '},
1203 StringSplitOptions.RemoveEmptyEntries);
1207 public BuildItemGroup EvaluatedItems {
1209 if (needToReevaluate) {
1210 needToReevaluate = false;
1213 return evaluatedItems;
1217 public BuildItemGroup EvaluatedItemsIgnoringCondition {
1219 if (needToReevaluate) {
1220 needToReevaluate = false;
1223 return evaluatedItemsIgnoringCondition;
1227 internal IDictionary <string, BuildItemGroup> EvaluatedItemsByName {
1229 // FIXME: do we need to do this here?
1230 if (needToReevaluate) {
1231 needToReevaluate = false;
1234 return evaluatedItemsByName;
1238 internal IEnumerable EvaluatedItemsByNameAsDictionaryEntries {
1240 if (EvaluatedItemsByName.Count == 0)
1243 foreach (KeyValuePair<string, BuildItemGroup> pair in EvaluatedItemsByName) {
1244 foreach (BuildItem bi in pair.Value)
1245 yield return new DictionaryEntry (pair.Key, bi.ConvertToITaskItem (null, ExpressionOptions.ExpandItemRefs));
1250 internal IDictionary <string, BuildItemGroup> EvaluatedItemsByNameIgnoringCondition {
1252 // FIXME: do we need to do this here?
1253 if (needToReevaluate) {
1254 needToReevaluate = false;
1257 return evaluatedItemsByNameIgnoringCondition;
1261 // For batching implementation
1262 Dictionary<string, BuildItemGroup> perBatchItemsByName;
1263 Dictionary<string, BuildItemGroup> commonItemsByName;
1266 public Dictionary<string, BuildItemGroup> perBatchItemsByName;
1267 public Dictionary<string, BuildItemGroup> commonItemsByName;
1269 public Batch (Dictionary<string, BuildItemGroup> perBatchItemsByName, Dictionary<string, BuildItemGroup> commonItemsByName)
1271 this.perBatchItemsByName = perBatchItemsByName;
1272 this.commonItemsByName = commonItemsByName;
1276 Stack<Batch> Batches {
1277 get { return batches; }
1280 internal void PushBatch (Dictionary<string, BuildItemGroup> perBatchItemsByName, Dictionary<string, BuildItemGroup> commonItemsByName)
1282 batches.Push (new Batch (perBatchItemsByName, commonItemsByName));
1283 SetBatchedItems (perBatchItemsByName, commonItemsByName);
1286 internal void PopBatch ()
1289 if (batches.Count > 0) {
1290 Batch b = batches.Peek ();
1291 SetBatchedItems (b.perBatchItemsByName, b.commonItemsByName);
1293 SetBatchedItems (null, null);
1297 void SetBatchedItems (Dictionary<string, BuildItemGroup> perBatchItemsByName, Dictionary<string, BuildItemGroup> commonItemsByName)
1299 this.perBatchItemsByName = perBatchItemsByName;
1300 this.commonItemsByName = commonItemsByName;
1304 internal bool TryGetEvaluatedItemByNameBatched (string itemName, out BuildItemGroup group)
1306 if (perBatchItemsByName != null && perBatchItemsByName.TryGetValue (itemName, out group))
1309 if (commonItemsByName != null && commonItemsByName.TryGetValue (itemName, out group))
1313 return EvaluatedItemsByName.TryGetValue (itemName, out group);
1316 internal string GetMetadataBatched (string itemName, string metadataName)
1318 BuildItemGroup group = null;
1319 if (itemName == null) {
1320 //unqualified, all items in a batch(bucket) have the
1321 //same metadata values
1322 group = GetFirst<BuildItemGroup> (perBatchItemsByName.Values);
1324 group = GetFirst<BuildItemGroup> (commonItemsByName.Values);
1327 TryGetEvaluatedItemByNameBatched (itemName, out group);
1330 if (group != null) {
1331 foreach (BuildItem item in group) {
1332 if (item.HasMetadata (metadataName))
1333 return item.GetEvaluatedMetadata (metadataName);
1336 return String.Empty;
1339 internal IEnumerable<BuildItemGroup> GetAllItemGroups ()
1341 if (perBatchItemsByName == null && commonItemsByName == null)
1342 foreach (BuildItemGroup group in EvaluatedItemsByName.Values)
1345 if (perBatchItemsByName != null)
1346 foreach (BuildItemGroup group in perBatchItemsByName.Values)
1349 if (commonItemsByName != null)
1350 foreach (BuildItemGroup group in commonItemsByName.Values)
1354 T GetFirst<T> (ICollection<T> list)
1359 foreach (T t in list)
1365 // Used for MSBuild*This* set of properties
1366 internal void PushThisFileProperty (string full_filename)
1368 string last_file = this_file_property_stack.Count == 0 ? String.Empty : this_file_property_stack.Peek ();
1369 this_file_property_stack.Push (full_filename);
1370 if (last_file != full_filename)
1371 // first time, or different from previous one
1372 SetMSBuildThisFileProperties (full_filename);
1375 internal void PopThisFileProperty ()
1377 string last_file = this_file_property_stack.Pop ();
1378 if (this_file_property_stack.Count > 0 && last_file != this_file_property_stack.Peek ())
1379 SetMSBuildThisFileProperties (this_file_property_stack.Peek ());
1382 void SetMSBuildThisFileProperties (string full_filename)
1384 if (String.IsNullOrEmpty (full_filename))
1387 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFile", Path.GetFileName (full_filename), PropertyType.Reserved));
1388 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFileFullPath", full_filename, PropertyType.Reserved));
1389 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFileName", Path.GetFileNameWithoutExtension (full_filename), PropertyType.Reserved));
1390 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFileExtension", Path.GetExtension (full_filename), PropertyType.Reserved));
1392 string project_dir = Path.GetDirectoryName (full_filename) + Path.DirectorySeparatorChar;
1393 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFileDirectory", project_dir, PropertyType.Reserved));
1394 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFileDirectoryNoRoot",
1395 project_dir.Substring (Path.GetPathRoot (project_dir).Length),
1396 PropertyType.Reserved));
1400 internal void LogWarning (string filename, string message, params object[] messageArgs)
1402 BuildWarningEventArgs bwea = new BuildWarningEventArgs (
1403 null, null, filename, 0, 0, 0, 0, String.Format (message, messageArgs),
1405 ParentEngine.EventSource.FireWarningRaised (this, bwea);
1408 internal void LogError (string filename, string message,
1409 params object[] messageArgs)
1411 BuildErrorEventArgs beea = new BuildErrorEventArgs (
1412 null, null, filename, 0, 0, 0, 0, String.Format (message, messageArgs),
1414 ParentEngine.EventSource.FireErrorRaised (this, beea);
1417 internal static string DefaultExtensionsPath {
1419 if (extensions_path == null) {
1420 // NOTE: code from mcs/tools/gacutil/driver.cs
1421 PropertyInfo gac = typeof (System.Environment).GetProperty (
1422 "GacPath", BindingFlags.Static | BindingFlags.NonPublic);
1425 MethodInfo get_gac = gac.GetGetMethod (true);
1426 string gac_path = (string) get_gac.Invoke (null, null);
1427 extensions_path = Path.GetFullPath (Path.Combine (
1428 gac_path, Path.Combine ("..", "xbuild")));
1431 return extensions_path;
1435 public BuildPropertyGroup EvaluatedProperties {
1437 if (needToReevaluate) {
1438 needToReevaluate = false;
1441 return evaluatedProperties;
1445 internal IEnumerable EvaluatedPropertiesAsDictionaryEntries {
1447 foreach (BuildProperty bp in EvaluatedProperties)
1448 yield return new DictionaryEntry (bp.Name, bp.Value);
1452 public string FullFileName {
1453 get { return fullFileName; }
1454 set { fullFileName = value; }
1457 public BuildPropertyGroup GlobalProperties {
1458 get { return globalProperties; }
1461 throw new ArgumentNullException ("value");
1464 throw new InvalidOperationException ("GlobalProperties can not be set to persisted property group.");
1466 globalProperties = value;
1470 public bool IsDirty {
1471 get { return isDirty; }
1474 public bool IsValidated {
1475 get { return isValidated; }
1476 set { isValidated = value; }
1479 public BuildItemGroupCollection ItemGroups {
1480 get { return itemGroups; }
1483 public ImportCollection Imports {
1484 get { return imports; }
1487 public string InitialTargets {
1489 return String.Join ("; ", initialTargets.ToArray ());
1492 initialTargets.Clear ();
1493 xmlDocument.DocumentElement.SetAttribute ("InitialTargets", value);
1495 initialTargets.AddRange (value.Split (
1496 new char [] {';', ' '}, StringSplitOptions.RemoveEmptyEntries));
1500 public Engine ParentEngine {
1501 get { return parentEngine; }
1504 public BuildPropertyGroupCollection PropertyGroups {
1505 get { return propertyGroups; }
1508 public string SchemaFile {
1509 get { return schemaFile; }
1510 set { schemaFile = value; }
1513 public TargetCollection Targets {
1514 get { return targets; }
1517 public DateTime TimeOfLastDirty {
1518 get { return timeOfLastDirty; }
1521 public UsingTaskCollection UsingTasks {
1522 get { return usingTasks; }
1527 get { return xmlDocument.InnerXml; }
1530 // corresponds to the xml attribute
1531 public string DefaultToolsVersion {
1533 if (xmlDocument != null)
1534 return xmlDocument.DocumentElement.GetAttribute ("ToolsVersion");
1538 if (xmlDocument != null)
1539 xmlDocument.DocumentElement.SetAttribute ("ToolsVersion", value);
1543 public bool HasToolsVersionAttribute {
1545 return xmlDocument != null && xmlDocument.DocumentElement.HasAttribute ("ToolsVersion");
1549 public string ToolsVersion {
1553 internal Dictionary <string, BuildItemGroup> LastItemGroupContaining {
1554 get { return last_item_group_containing; }
1557 internal static XmlNamespaceManager XmlNamespaceManager {
1559 if (manager == null) {
1560 manager = new XmlNamespaceManager (new NameTable ());
1561 manager.AddNamespace ("tns", ns);
1568 internal TaskDatabase TaskDatabase {
1569 get { return taskDatabase; }
1572 internal XmlDocument XmlDocument {
1573 get { return xmlDocument; }
1576 internal static string XmlNamespace {
1582 PlatformID pid = Environment.OSVersion.Platform;
1590 return "Windows_NT";