2 // Project.cs: Project class
5 // Marek Sieradzki (marek.sieradzki@gmail.com)
7 // (C) 2005 Marek Sieradzki
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 using System.Collections;
32 using System.Collections.Generic;
33 using System.Collections.Specialized;
36 using System.Reflection;
39 using System.Xml.Schema;
40 using Microsoft.Build.Framework;
41 using Mono.XBuild.Framework;
42 using Mono.XBuild.CommandLine;
44 namespace Microsoft.Build.BuildEngine {
45 public class Project {
48 Dictionary <string, List <string>> conditionedProperties;
49 string[] defaultTargets;
51 BuildItemGroup evaluatedItems;
52 BuildItemGroup evaluatedItemsIgnoringCondition;
53 Dictionary <string, BuildItemGroup> evaluatedItemsByName;
54 Dictionary <string, BuildItemGroup> evaluatedItemsByNameIgnoringCondition;
55 BuildPropertyGroup evaluatedProperties;
56 string firstTargetName;
58 BuildPropertyGroup globalProperties;
59 GroupingCollection groupingCollection;
62 BuildItemGroupCollection itemGroups;
63 ImportCollection imports;
64 List<string> initialTargets;
65 Dictionary <string, BuildItemGroup> last_item_group_containing;
66 bool needToReevaluate;
68 BuildPropertyGroupCollection propertyGroups;
70 TaskDatabase taskDatabase;
71 TargetCollection targets;
72 DateTime timeOfLastDirty;
73 UsingTaskCollection usingTasks;
74 XmlDocument xmlDocument;
76 bool initialTargetsBuilt;
78 BuildSettings current_settings;
81 // This is used to keep track of "current" file,
82 // which is then used to set the reserved properties
83 // $(MSBuildThisFile*)
84 Stack<string> this_file_property_stack;
85 ProjectLoadSettings project_load_settings;
88 static string extensions_path;
89 static XmlNamespaceManager manager;
90 static string ns = "http://schemas.microsoft.com/developer/msbuild/2003";
93 : this (Engine.GlobalEngine)
97 public Project (Engine engine) : this (engine, null)
101 public Project (Engine engine, string toolsVersion)
103 parentEngine = engine;
104 ToolsVersion = toolsVersion;
106 buildEnabled = ParentEngine.BuildEnabled;
107 xmlDocument = new XmlDocument ();
108 xmlDocument.PreserveWhitespace = false;
109 xmlDocument.AppendChild (xmlDocument.CreateElement ("Project", XmlNamespace));
110 xmlDocument.DocumentElement.SetAttribute ("xmlns", ns);
112 fullFileName = String.Empty;
113 timeOfLastDirty = DateTime.Now;
114 current_settings = BuildSettings.None;
115 project_load_settings = ProjectLoadSettings.None;
119 initialTargets = new List<string> ();
120 defaultTargets = new string [0];
121 batches = new Stack<Batch> ();
122 this_file_property_stack = new Stack<string> ();
124 globalProperties = new BuildPropertyGroup (null, this, null, false);
125 foreach (BuildProperty bp in parentEngine.GlobalProperties)
126 GlobalProperties.AddProperty (bp.Clone (true));
132 [MonoTODO ("Not tested")]
133 public void AddNewImport (string importLocation,
134 string importCondition)
136 if (importLocation == null)
137 throw new ArgumentNullException ("importLocation");
139 XmlElement importElement = xmlDocument.CreateElement ("Import", XmlNamespace);
140 xmlDocument.DocumentElement.AppendChild (importElement);
141 importElement.SetAttribute ("Project", importLocation);
142 if (!String.IsNullOrEmpty (importCondition))
143 importElement.SetAttribute ("Condition", importCondition);
145 Import import = new Import (importElement, this, null);
146 imports.Add (import);
147 MarkProjectAsDirty ();
151 public BuildItem AddNewItem (string itemName,
154 return AddNewItem (itemName, itemInclude, false);
157 [MonoTODO ("Adds item not in the same place as MS")]
158 public BuildItem AddNewItem (string itemName,
160 bool treatItemIncludeAsLiteral)
164 if (itemGroups.Count == 0)
165 big = AddNewItemGroup ();
167 if (last_item_group_containing.ContainsKey (itemName)) {
168 big = last_item_group_containing [itemName];
171 BuildItemGroup [] groups = new BuildItemGroup [itemGroups.Count];
172 itemGroups.CopyTo (groups, 0);
177 BuildItem item = big.AddNewItem (itemName, itemInclude, treatItemIncludeAsLiteral);
179 MarkProjectAsDirty ();
185 [MonoTODO ("Not tested")]
186 public BuildItemGroup AddNewItemGroup ()
188 XmlElement element = xmlDocument.CreateElement ("ItemGroup", XmlNamespace);
189 xmlDocument.DocumentElement.AppendChild (element);
191 BuildItemGroup big = new BuildItemGroup (element, this, null, false);
192 itemGroups.Add (big);
193 MarkProjectAsDirty ();
199 [MonoTODO ("Ignores insertAtEndOfProject")]
200 public BuildPropertyGroup AddNewPropertyGroup (bool insertAtEndOfProject)
202 XmlElement element = xmlDocument.CreateElement ("PropertyGroup", XmlNamespace);
203 xmlDocument.DocumentElement.AppendChild (element);
205 BuildPropertyGroup bpg = new BuildPropertyGroup (element, this, null, false);
206 propertyGroups.Add (bpg);
207 MarkProjectAsDirty ();
213 [MonoTODO ("Not tested, isn't added to TaskDatabase (no reevaluation)")]
214 public void AddNewUsingTaskFromAssemblyFile (string taskName,
217 if (taskName == null)
218 throw new ArgumentNullException ("taskName");
219 if (assemblyFile == null)
220 throw new ArgumentNullException ("assemblyFile");
222 XmlElement element = xmlDocument.CreateElement ("UsingTask", XmlNamespace);
223 xmlDocument.DocumentElement.AppendChild (element);
224 element.SetAttribute ("TaskName", taskName);
225 element.SetAttribute ("AssemblyFile", assemblyFile);
227 UsingTask ut = new UsingTask (element, this, null);
229 MarkProjectAsDirty ();
232 [MonoTODO ("Not tested, isn't added to TaskDatabase (no reevaluation)")]
233 public void AddNewUsingTaskFromAssemblyName (string taskName,
236 if (taskName == null)
237 throw new ArgumentNullException ("taskName");
238 if (assemblyName == null)
239 throw new ArgumentNullException ("assemblyName");
241 XmlElement element = xmlDocument.CreateElement ("UsingTask", XmlNamespace);
242 xmlDocument.DocumentElement.AppendChild (element);
243 element.SetAttribute ("TaskName", taskName);
244 element.SetAttribute ("AssemblyName", assemblyName);
246 UsingTask ut = new UsingTask (element, this, null);
248 MarkProjectAsDirty ();
251 [MonoTODO ("Not tested")]
254 return Build (new string [0]);
257 [MonoTODO ("Not tested")]
258 public bool Build (string targetName)
260 if (targetName == null)
261 return Build ((string[]) null);
263 return Build (new string [1] { targetName });
266 [MonoTODO ("Not tested")]
267 public bool Build (string [] targetNames)
269 return Build (targetNames, null);
272 [MonoTODO ("Not tested")]
273 public bool Build (string [] targetNames,
274 IDictionary targetOutputs)
276 return Build (targetNames, targetOutputs, BuildSettings.None);
279 [MonoTODO ("Not tested")]
280 public bool Build (string [] targetNames,
281 IDictionary targetOutputs,
282 BuildSettings buildFlags)
286 ParentEngine.StartProjectBuild (this, targetNames);
288 // Invoking this to emit a warning in case of unsupported
290 GetToolsVersionToUse (true);
292 string current_directory = Environment.CurrentDirectory;
294 current_settings = buildFlags;
295 if (!String.IsNullOrEmpty (fullFileName))
296 Directory.SetCurrentDirectory (Path.GetDirectoryName (fullFileName));
298 result = BuildInternal (targetNames, targetOutputs, buildFlags);
300 ParentEngine.EndProjectBuild (this, result);
301 current_settings = BuildSettings.None;
302 Directory.SetCurrentDirectory (current_directory);
309 bool BuildInternal (string [] targetNames,
310 IDictionary targetOutputs,
311 BuildSettings buildFlags)
314 if (buildFlags == BuildSettings.None) {
315 needToReevaluate = false;
320 ProcessBeforeAndAfterTargets ();
323 if (targetNames == null || targetNames.Length == 0) {
324 if (defaultTargets != null && defaultTargets.Length != 0) {
325 targetNames = defaultTargets;
326 } else if (firstTargetName != null) {
327 targetNames = new string [1] { firstTargetName};
329 if (targets == null || targets.Count == 0) {
330 LogError (fullFileName, "No target found in the project");
338 if (!initialTargetsBuilt) {
339 foreach (string target in initialTargets) {
340 if (!BuildTarget (target.Trim (), targetOutputs))
343 initialTargetsBuilt = true;
346 foreach (string target in targetNames)
347 if (!BuildTarget (target.Trim (), targetOutputs))
353 bool BuildTarget (string target_name, IDictionary targetOutputs)
355 if (target_name == null)
356 throw new ArgumentException ("targetNames cannot contain null strings");
358 if (!targets.Exists (target_name)) {
359 LogError (fullFileName, "Target named '{0}' not found in the project.", target_name);
363 string key = GetKeyForTarget (target_name);
364 if (!targets [target_name].Build (key))
368 if (ParentEngine.BuiltTargetsOutputByName.TryGetValue (key, out outputs)) {
369 if (targetOutputs != null)
370 targetOutputs.Add (target_name, outputs);
375 internal string GetKeyForTarget (string target_name)
377 return GetKeyForTarget (target_name, true);
380 internal string GetKeyForTarget (string target_name, bool include_global_properties)
382 // target name is case insensitive
383 return fullFileName + ":" + target_name.ToLower () +
384 (include_global_properties ? (":" + GlobalPropertiesToString (GlobalProperties))
388 string GlobalPropertiesToString (BuildPropertyGroup bgp)
390 StringBuilder sb = new StringBuilder ();
391 foreach (BuildProperty bp in bgp)
392 sb.AppendFormat (" {0}:{1}", bp.Name, bp.FinalValue);
393 return sb.ToString ();
397 void ProcessBeforeAndAfterTargets ()
399 var beforeTable = Targets.AsIEnumerable ()
400 .SelectMany (target => GetTargetNamesFromString (target.BeforeTargets),
401 (target, before_target) => new {before_target, name = target.Name})
402 .ToLookup (x => x.before_target, x => x.name)
403 .ToDictionary (x => x.Key, x => x.Distinct ().ToList ());
405 foreach (var pair in beforeTable) {
406 if (targets.Exists (pair.Key))
407 targets [pair.Key].BeforeThisTargets = pair.Value;
409 LogWarning (FullFileName, "Target '{0}', not found in the project", pair.Key);
412 var afterTable = Targets.AsIEnumerable ()
413 .SelectMany (target => GetTargetNamesFromString (target.AfterTargets),
414 (target, after_target) => new {after_target, name = target.Name})
415 .ToLookup (x => x.after_target, x => x.name)
416 .ToDictionary (x => x.Key, x => x.Distinct ().ToList ());
418 foreach (var pair in afterTable) {
419 if (targets.Exists (pair.Key))
420 targets [pair.Key].AfterThisTargets = pair.Value;
422 LogWarning (FullFileName, "Target '{0}', not found in the project", pair.Key);
426 string[] GetTargetNamesFromString (string targets)
428 Expression expr = new Expression ();
429 expr.Parse (targets, ParseOptions.AllowItemsNoMetadataAndSplit);
430 return (string []) expr.ConvertTo (this, typeof (string []));
435 public string [] GetConditionedPropertyValues (string propertyName)
437 if (conditionedProperties.ContainsKey (propertyName))
438 return conditionedProperties [propertyName].ToArray ();
440 return new string [0];
443 public BuildItemGroup GetEvaluatedItemsByName (string itemName)
445 if (needToReevaluate) {
446 needToReevaluate = false;
450 if (evaluatedItemsByName.ContainsKey (itemName))
451 return evaluatedItemsByName [itemName];
453 return new BuildItemGroup (this);
456 public BuildItemGroup GetEvaluatedItemsByNameIgnoringCondition (string itemName)
458 if (needToReevaluate) {
459 needToReevaluate = false;
463 if (evaluatedItemsByNameIgnoringCondition.ContainsKey (itemName))
464 return evaluatedItemsByNameIgnoringCondition [itemName];
466 return new BuildItemGroup (this);
469 public string GetEvaluatedProperty (string propertyName)
471 if (needToReevaluate) {
472 needToReevaluate = false;
476 if (propertyName == null)
477 throw new ArgumentNullException ("propertyName");
479 BuildProperty bp = evaluatedProperties [propertyName];
481 return bp == null ? null : (string) bp;
484 [MonoTODO ("We should remember that node and not use XPath to get it")]
485 public string GetProjectExtensions (string id)
487 if (id == null || id == String.Empty)
490 XmlNode node = xmlDocument.SelectSingleNode (String.Format ("/tns:Project/tns:ProjectExtensions/tns:{0}", id), XmlNamespaceManager);
495 return node.InnerXml;
499 public void Load (string projectFileName)
501 Load (projectFileName, ProjectLoadSettings.None);
504 public void Load (string projectFileName, ProjectLoadSettings settings)
506 project_load_settings = settings;
507 if (String.IsNullOrEmpty (projectFileName))
508 throw new ArgumentNullException ("projectFileName");
510 if (!File.Exists (projectFileName))
511 throw new ArgumentException (String.Format ("Project file {0} not found", projectFileName),
514 this.fullFileName = Utilities.FromMSBuildPath (Path.GetFullPath (projectFileName));
515 PushThisFileProperty (fullFileName);
517 string filename = fullFileName;
518 if (String.Compare (Path.GetExtension (fullFileName), ".sln", true) == 0) {
519 Project tmp_project = ParentEngine.CreateNewProject ();
520 SolutionParser sln_parser = new SolutionParser ();
521 sln_parser.ParseSolution (fullFileName, tmp_project, delegate (int errorNumber, string message) {
522 LogWarning (filename, message);
524 filename = fullFileName + ".proj";
526 tmp_project.Save (filename);
527 ParentEngine.RemoveLoadedProject (tmp_project);
528 DoLoad (new StreamReader (filename));
530 if (Environment.GetEnvironmentVariable ("XBUILD_EMIT_SOLUTION") == null)
531 File.Delete (filename);
534 DoLoad (new StreamReader (filename));
538 [MonoTODO ("Not tested")]
539 public void Load (TextReader textReader)
541 Load (textReader, ProjectLoadSettings.None);
544 public void Load (TextReader textReader, ProjectLoadSettings projectLoadSettings)
546 project_load_settings = projectLoadSettings;
547 fullFileName = String.Empty;
551 public void LoadXml (string projectXml)
553 LoadXml (projectXml, ProjectLoadSettings.None);
556 public void LoadXml (string projectXml, ProjectLoadSettings projectLoadSettings)
558 project_load_settings = projectLoadSettings;
559 fullFileName = String.Empty;
560 DoLoad (new StringReader (projectXml));
561 MarkProjectAsDirty ();
565 public void MarkProjectAsDirty ()
568 timeOfLastDirty = DateTime.Now;
571 [MonoTODO ("Not tested")]
572 public void RemoveAllItemGroups ()
574 int length = ItemGroups.Count;
575 BuildItemGroup [] groups = new BuildItemGroup [length];
576 ItemGroups.CopyTo (groups, 0);
578 for (int i = 0; i < length; i++)
579 RemoveItemGroup (groups [i]);
581 MarkProjectAsDirty ();
585 [MonoTODO ("Not tested")]
586 public void RemoveAllPropertyGroups ()
588 int length = PropertyGroups.Count;
589 BuildPropertyGroup [] groups = new BuildPropertyGroup [length];
590 PropertyGroups.CopyTo (groups, 0);
592 for (int i = 0; i < length; i++)
593 RemovePropertyGroup (groups [i]);
595 MarkProjectAsDirty ();
600 public void RemoveItem (BuildItem itemToRemove)
602 if (itemToRemove == null)
603 throw new ArgumentNullException ("itemToRemove");
605 if (!itemToRemove.FromXml && !itemToRemove.HasParentItem)
606 throw new InvalidOperationException ("The object passed in is not part of the project.");
608 BuildItemGroup big = itemToRemove.ParentItemGroup;
610 if (big.Count == 1) {
611 // ParentItemGroup for items from xml and that have parent is the same
612 groupingCollection.Remove (big);
614 if (big.ParentProject != this)
615 throw new InvalidOperationException ("The object passed in is not part of the project.");
617 if (itemToRemove.FromXml)
618 big.RemoveItem (itemToRemove);
620 big.RemoveItem (itemToRemove.ParentItem);
623 MarkProjectAsDirty ();
627 [MonoTODO ("Not tested")]
628 public void RemoveItemGroup (BuildItemGroup itemGroupToRemove)
630 if (itemGroupToRemove == null)
631 throw new ArgumentNullException ("itemGroupToRemove");
633 groupingCollection.Remove (itemGroupToRemove);
634 MarkProjectAsDirty ();
638 // NOTE: does not modify imported projects
639 public void RemoveItemGroupsWithMatchingCondition (string matchingCondition)
641 throw new NotImplementedException ();
645 public void RemoveItemsByName (string itemName)
647 if (itemName == null)
648 throw new ArgumentNullException ("itemName");
650 throw new NotImplementedException ();
653 [MonoTODO ("Not tested")]
654 public void RemovePropertyGroup (BuildPropertyGroup propertyGroupToRemove)
656 if (propertyGroupToRemove == null)
657 throw new ArgumentNullException ("propertyGroupToRemove");
659 groupingCollection.Remove (propertyGroupToRemove);
660 MarkProjectAsDirty ();
664 // NOTE: does not modify imported projects
665 public void RemovePropertyGroupsWithMatchingCondition (string matchCondition)
667 throw new NotImplementedException ();
671 public void ResetBuildStatus ()
673 // hack to allow built targets to be removed
679 public void Save (string projectFileName)
681 Save (projectFileName, Encoding.Default);
685 [MonoTODO ("Ignores encoding")]
686 public void Save (string projectFileName, Encoding encoding)
688 xmlDocument.Save (projectFileName);
692 public void Save (TextWriter outTextWriter)
694 xmlDocument.Save (outTextWriter);
698 public void SetImportedProperty (string propertyName,
699 string propertyValue,
701 Project importProject)
703 SetImportedProperty (propertyName, propertyValue, condition, importProject,
704 PropertyPosition.UseExistingOrCreateAfterLastPropertyGroup);
707 public void SetImportedProperty (string propertyName,
708 string propertyValue,
710 Project importedProject,
711 PropertyPosition position)
713 SetImportedProperty (propertyName, propertyValue, condition, importedProject,
714 PropertyPosition.UseExistingOrCreateAfterLastPropertyGroup, false);
718 public void SetImportedProperty (string propertyName,
719 string propertyValue,
721 Project importedProject,
722 PropertyPosition position,
723 bool treatPropertyValueAsLiteral)
725 throw new NotImplementedException ();
728 public void SetProjectExtensions (string id, string xmlText)
731 throw new ArgumentNullException ("id");
733 throw new ArgumentNullException ("xmlText");
735 XmlNode projectExtensions, node;
737 projectExtensions = xmlDocument.SelectSingleNode ("/tns:Project/tns:ProjectExtensions", XmlNamespaceManager);
739 if (projectExtensions == null) {
740 projectExtensions = xmlDocument.CreateElement ("ProjectExtensions", XmlNamespace);
741 xmlDocument.DocumentElement.AppendChild (projectExtensions);
743 node = xmlDocument.CreateElement (id, XmlNamespace);
744 node.InnerXml = xmlText;
745 projectExtensions.AppendChild (node);
747 node = xmlDocument.SelectSingleNode (String.Format ("/tns:Project/tns:ProjectExtensions/tns:{0}", id), XmlNamespaceManager);
750 node = xmlDocument.CreateElement (id, XmlNamespace);
751 projectExtensions.AppendChild (node);
754 node.InnerXml = xmlText;
758 MarkProjectAsDirty ();
761 public void SetProperty (string propertyName,
762 string propertyValue)
764 SetProperty (propertyName, propertyValue, "true",
765 PropertyPosition.UseExistingOrCreateAfterLastPropertyGroup, false);
768 public void SetProperty (string propertyName,
769 string propertyValue,
772 SetProperty (propertyName, propertyValue, condition,
773 PropertyPosition.UseExistingOrCreateAfterLastPropertyGroup);
776 public void SetProperty (string propertyName,
777 string propertyValue,
779 PropertyPosition position)
781 SetProperty (propertyName, propertyValue, condition,
782 PropertyPosition.UseExistingOrCreateAfterLastPropertyGroup, false);
786 public void SetProperty (string propertyName,
787 string propertyValue,
789 PropertyPosition position,
790 bool treatPropertyValueAsLiteral)
792 throw new NotImplementedException ();
795 internal void Unload ()
800 internal void CheckUnloaded ()
803 throw new InvalidOperationException ("This project object has been unloaded from the MSBuild engine and is no longer valid.");
806 internal void NeedToReevaluate ()
808 needToReevaluate = true;
811 // Does the actual loading.
812 void DoLoad (TextReader textReader)
815 ParentEngine.RemoveLoadedProject (this);
817 xmlDocument.Load (textReader);
819 if (xmlDocument.DocumentElement.Name == "VisualStudioProject")
820 throw new InvalidProjectFileException (String.Format (
821 "Project file '{0}' is a VS2003 project, which is not " +
822 "supported by xbuild. You need to convert it to msbuild " +
823 "format to build with xbuild.", fullFileName));
825 if (SchemaFile != null) {
826 xmlDocument.Schemas.Add (XmlSchema.Read (
827 new StreamReader (SchemaFile), ValidationCallBack));
828 xmlDocument.Validate (ValidationCallBack);
831 if (xmlDocument.DocumentElement.Name != "Project") {
832 throw new InvalidProjectFileException (String.Format (
833 "The element <{0}> is unrecognized, or not supported in this context.", xmlDocument.DocumentElement.Name));
836 if (xmlDocument.DocumentElement.GetAttribute ("xmlns") != ns) {
837 throw new InvalidProjectFileException (
838 @"The default XML namespace of the project must be the MSBuild XML namespace." +
839 " If the project is authored in the MSBuild 2003 format, please add " +
840 "xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\" to the <Project> element. " +
841 "If the project has been authored in the old 1.0 or 1.2 format, please convert it to MSBuild 2003 format. ");
844 ParentEngine.AddLoadedProject (this);
845 } catch (Exception e) {
846 throw new InvalidProjectFileException (String.Format ("{0}: {1}",
847 fullFileName, e.Message), e);
849 if (textReader != null)
861 groupingCollection = new GroupingCollection (this);
862 imports = new ImportCollection (groupingCollection);
863 usingTasks = new UsingTaskCollection (this);
864 itemGroups = new BuildItemGroupCollection (groupingCollection);
865 propertyGroups = new BuildPropertyGroupCollection (groupingCollection);
866 targets = new TargetCollection (this);
867 last_item_group_containing = new Dictionary <string, BuildItemGroup> ();
869 string effective_tools_version = GetToolsVersionToUse (false);
870 taskDatabase = new TaskDatabase ();
871 taskDatabase.CopyTasks (ParentEngine.GetDefaultTasks (effective_tools_version));
873 initialTargets = new List<string> ();
874 defaultTargets = new string [0];
875 PrepareForEvaluate (effective_tools_version);
876 ProcessElements (xmlDocument.DocumentElement, null);
882 void ProcessProjectAttributes (XmlAttributeCollection attributes)
884 foreach (XmlAttribute attr in attributes) {
886 case "InitialTargets":
887 initialTargets.AddRange (attr.Value.Split (
888 new char [] {';', ' '},
889 StringSplitOptions.RemoveEmptyEntries));
891 case "DefaultTargets":
892 // first non-empty DefaultTargets found is used
893 if (defaultTargets == null || defaultTargets.Length == 0)
894 defaultTargets = attr.Value.Split (new char [] {';', ' '},
895 StringSplitOptions.RemoveEmptyEntries);
896 EvaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectDefaultTargets",
897 DefaultTargets, PropertyType.Reserved));
903 internal void ProcessElements (XmlElement rootElement, ImportedProject ip)
905 ProcessProjectAttributes (rootElement.Attributes);
906 foreach (XmlNode xn in rootElement.ChildNodes) {
907 if (xn is XmlElement) {
908 XmlElement xe = (XmlElement) xn;
910 case "ProjectExtensions":
911 AddProjectExtensions (xe);
922 AddUsingTask (xe, ip);
928 AddItemGroup (xe, ip);
930 case "PropertyGroup":
931 AddPropertyGroup (xe, ip);
937 throw new InvalidProjectFileException (String.Format ("Invalid element '{0}' in project file.", xe.Name));
943 void PrepareForEvaluate (string effective_tools_version)
945 evaluatedItems = new BuildItemGroup (null, this, null, true);
946 evaluatedItemsIgnoringCondition = new BuildItemGroup (null, this, null, true);
947 evaluatedItemsByName = new Dictionary <string, BuildItemGroup> (StringComparer.InvariantCultureIgnoreCase);
948 evaluatedItemsByNameIgnoringCondition = new Dictionary <string, BuildItemGroup> (StringComparer.InvariantCultureIgnoreCase);
949 if (building && current_settings == BuildSettings.None)
950 RemoveBuiltTargets ();
952 InitializeProperties (effective_tools_version);
957 groupingCollection.Evaluate ();
959 //FIXME: UsingTasks aren't really evaluated. (shouldn't use expressions or anything)
960 foreach (UsingTask usingTask in UsingTasks)
961 usingTask.Evaluate ();
964 // Removes entries of all earlier built targets for this project
965 void RemoveBuiltTargets ()
967 ParentEngine.ClearBuiltTargetsForProject (this);
970 void InitializeProperties (string effective_tools_version)
974 evaluatedProperties = new BuildPropertyGroup (null, null, null, true);
975 conditionedProperties = new Dictionary<string, List<string>> ();
977 foreach (BuildProperty gp in GlobalProperties) {
978 bp = new BuildProperty (gp.Name, gp.Value, PropertyType.Global);
979 evaluatedProperties.AddProperty (bp);
982 foreach (BuildProperty gp in GlobalProperties)
983 ParentEngine.GlobalProperties.AddProperty (gp);
985 // add properties that we dont have from parent engine's
987 foreach (BuildProperty gp in ParentEngine.GlobalProperties) {
988 if (evaluatedProperties [gp.Name] == null) {
989 bp = new BuildProperty (gp.Name, gp.Value, PropertyType.Global);
990 evaluatedProperties.AddProperty (bp);
994 foreach (DictionaryEntry de in Environment.GetEnvironmentVariables ()) {
995 bp = new BuildProperty ((string) de.Key, (string) de.Value, PropertyType.Environment);
996 evaluatedProperties.AddProperty (bp);
999 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectFile", Path.GetFileName (fullFileName),
1000 PropertyType.Reserved));
1001 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectFullPath", fullFileName, PropertyType.Reserved));
1002 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectName",
1003 Path.GetFileNameWithoutExtension (fullFileName),
1004 PropertyType.Reserved));
1005 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectExtension",
1006 Path.GetExtension (fullFileName),
1007 PropertyType.Reserved));
1009 string toolsPath = parentEngine.Toolsets [effective_tools_version].ToolsPath;
1010 if (toolsPath == null)
1011 throw new Exception (String.Format ("Invalid tools version '{0}', no tools path set for this.", effective_tools_version));
1012 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildBinPath", toolsPath, PropertyType.Reserved));
1013 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildToolsPath", toolsPath, PropertyType.Reserved));
1014 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildToolsRoot", Path.GetDirectoryName (toolsPath), PropertyType.Reserved));
1015 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildToolsVersion", effective_tools_version, PropertyType.Reserved));
1016 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildExtensionsPath", ExtensionsPath, PropertyType.Reserved));
1017 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildExtensionsPath32", ExtensionsPath, PropertyType.Reserved));
1018 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildExtensionsPath64", ExtensionsPath, PropertyType.Reserved));
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 ());
1038 // ToolsVersion property
1039 // ToolsVersion attribute on the project
1040 // parentEngine's DefaultToolsVersion
1041 string GetToolsVersionToUse (bool emitWarning)
1043 if (!String.IsNullOrEmpty (ToolsVersion))
1044 return ToolsVersion;
1046 if (!HasToolsVersionAttribute)
1047 return parentEngine.DefaultToolsVersion;
1049 if (parentEngine.Toolsets [DefaultToolsVersion] == null) {
1051 LogWarning (FullFileName, "Project has unknown ToolsVersion '{0}'. Using the default tools version '{1}' instead.",
1052 DefaultToolsVersion, parentEngine.DefaultToolsVersion);
1053 return parentEngine.DefaultToolsVersion;
1056 return DefaultToolsVersion;
1059 void AddProjectExtensions (XmlElement xmlElement)
1063 void AddMessage (XmlElement xmlElement)
1067 void AddTarget (XmlElement xmlElement, ImportedProject importedProject)
1069 Target target = new Target (xmlElement, this, importedProject);
1070 targets.AddTarget (target);
1072 if (firstTargetName == null)
1073 firstTargetName = target.Name;
1076 void AddUsingTask (XmlElement xmlElement, ImportedProject importedProject)
1078 UsingTask usingTask;
1080 usingTask = new UsingTask (xmlElement, this, importedProject);
1081 UsingTasks.Add (usingTask);
1084 void AddImport (XmlElement xmlElement, ImportedProject importingProject)
1086 // eval all the properties etc till the import
1087 groupingCollection.Evaluate (EvaluationType.Property);
1089 Import import = new Import (xmlElement, this, importingProject);
1090 if (!ConditionParser.ParseAndEvaluate (import.Condition, this))
1093 if (Imports.Contains (import)) {
1094 LogWarning (importingProject != null ? importingProject.FullFileName : fullFileName,
1095 "A circular reference was found involving the import of {0}. Only" +
1096 " the first import of this file will be used, ignoring others.",
1097 import.ProjectPath);
1102 if (String.Compare (fullFileName, import.EvaluatedProjectPath) == 0) {
1103 LogWarning (importingProject != null ? importingProject.FullFileName : fullFileName,
1104 "The main project file was imported here, which creates a circular " +
1105 "reference. Ignoring this import.");
1110 Imports.Add (import);
1111 import.Evaluate (project_load_settings == ProjectLoadSettings.IgnoreMissingImports);
1114 void AddItemGroup (XmlElement xmlElement, ImportedProject importedProject)
1116 BuildItemGroup big = new BuildItemGroup (xmlElement, this, importedProject, false);
1117 ItemGroups.Add (big);
1120 void AddPropertyGroup (XmlElement xmlElement, ImportedProject importedProject)
1122 BuildPropertyGroup bpg = new BuildPropertyGroup (xmlElement, this, importedProject, false);
1123 PropertyGroups.Add (bpg);
1126 void AddChoose (XmlElement xmlElement, ImportedProject importedProject)
1128 BuildChoose bc = new BuildChoose (xmlElement, this, importedProject);
1129 groupingCollection.Add (bc);
1132 static void ValidationCallBack (object sender, ValidationEventArgs e)
1134 Console.WriteLine ("Validation Error: {0}", e.Message);
1137 public bool BuildEnabled {
1139 return buildEnabled;
1142 buildEnabled = value;
1147 public Encoding Encoding {
1148 get { return encoding; }
1151 public string DefaultTargets {
1153 return String.Join ("; ", defaultTargets);
1156 xmlDocument.DocumentElement.SetAttribute ("DefaultTargets", value);
1158 defaultTargets = value.Split (new char [] {';', ' '},
1159 StringSplitOptions.RemoveEmptyEntries);
1163 public BuildItemGroup EvaluatedItems {
1165 if (needToReevaluate) {
1166 needToReevaluate = false;
1169 return evaluatedItems;
1173 public BuildItemGroup EvaluatedItemsIgnoringCondition {
1175 if (needToReevaluate) {
1176 needToReevaluate = false;
1179 return evaluatedItemsIgnoringCondition;
1183 internal IDictionary <string, BuildItemGroup> EvaluatedItemsByName {
1185 // FIXME: do we need to do this here?
1186 if (needToReevaluate) {
1187 needToReevaluate = false;
1190 return evaluatedItemsByName;
1194 internal IEnumerable EvaluatedItemsByNameAsDictionaryEntries {
1196 if (EvaluatedItemsByName.Count == 0)
1199 foreach (KeyValuePair<string, BuildItemGroup> pair in EvaluatedItemsByName) {
1200 foreach (BuildItem bi in pair.Value)
1201 yield return new DictionaryEntry (pair.Key, bi.ConvertToITaskItem (null, ExpressionOptions.ExpandItemRefs));
1206 internal IDictionary <string, BuildItemGroup> EvaluatedItemsByNameIgnoringCondition {
1208 // FIXME: do we need to do this here?
1209 if (needToReevaluate) {
1210 needToReevaluate = false;
1213 return evaluatedItemsByNameIgnoringCondition;
1217 // For batching implementation
1218 Dictionary<string, BuildItemGroup> perBatchItemsByName;
1219 Dictionary<string, BuildItemGroup> commonItemsByName;
1222 public Dictionary<string, BuildItemGroup> perBatchItemsByName;
1223 public Dictionary<string, BuildItemGroup> commonItemsByName;
1225 public Batch (Dictionary<string, BuildItemGroup> perBatchItemsByName, Dictionary<string, BuildItemGroup> commonItemsByName)
1227 this.perBatchItemsByName = perBatchItemsByName;
1228 this.commonItemsByName = commonItemsByName;
1232 Stack<Batch> Batches {
1233 get { return batches; }
1236 internal void PushBatch (Dictionary<string, BuildItemGroup> perBatchItemsByName, Dictionary<string, BuildItemGroup> commonItemsByName)
1238 batches.Push (new Batch (perBatchItemsByName, commonItemsByName));
1239 SetBatchedItems (perBatchItemsByName, commonItemsByName);
1242 internal void PopBatch ()
1245 if (batches.Count > 0) {
1246 Batch b = batches.Peek ();
1247 SetBatchedItems (b.perBatchItemsByName, b.commonItemsByName);
1249 SetBatchedItems (null, null);
1253 void SetBatchedItems (Dictionary<string, BuildItemGroup> perBatchItemsByName, Dictionary<string, BuildItemGroup> commonItemsByName)
1255 this.perBatchItemsByName = perBatchItemsByName;
1256 this.commonItemsByName = commonItemsByName;
1260 internal bool TryGetEvaluatedItemByNameBatched (string itemName, out BuildItemGroup group)
1262 if (perBatchItemsByName != null && perBatchItemsByName.TryGetValue (itemName, out group))
1265 if (commonItemsByName != null && commonItemsByName.TryGetValue (itemName, out group))
1269 return EvaluatedItemsByName.TryGetValue (itemName, out group);
1272 internal string GetMetadataBatched (string itemName, string metadataName)
1274 BuildItemGroup group = null;
1275 if (itemName == null) {
1276 //unqualified, all items in a batch(bucket) have the
1277 //same metadata values
1278 group = GetFirst<BuildItemGroup> (perBatchItemsByName.Values);
1280 group = GetFirst<BuildItemGroup> (commonItemsByName.Values);
1283 TryGetEvaluatedItemByNameBatched (itemName, out group);
1286 if (group != null) {
1287 foreach (BuildItem item in group) {
1288 if (item.HasMetadata (metadataName))
1289 return item.GetEvaluatedMetadata (metadataName);
1292 return String.Empty;
1295 internal IEnumerable<BuildItemGroup> GetAllItemGroups ()
1297 if (perBatchItemsByName == null && commonItemsByName == null)
1298 foreach (BuildItemGroup group in EvaluatedItemsByName.Values)
1301 if (perBatchItemsByName != null)
1302 foreach (BuildItemGroup group in perBatchItemsByName.Values)
1305 if (commonItemsByName != null)
1306 foreach (BuildItemGroup group in commonItemsByName.Values)
1310 T GetFirst<T> (ICollection<T> list)
1315 foreach (T t in list)
1321 // Used for MSBuild*This* set of properties
1322 internal void PushThisFileProperty (string full_filename)
1324 string last_file = this_file_property_stack.Count == 0 ? String.Empty : this_file_property_stack.Peek ();
1325 this_file_property_stack.Push (full_filename);
1326 if (last_file != full_filename)
1327 // first time, or different from previous one
1328 SetMSBuildThisFileProperties (full_filename);
1331 internal void PopThisFileProperty ()
1333 string last_file = this_file_property_stack.Pop ();
1334 if (this_file_property_stack.Count > 0 && last_file != this_file_property_stack.Peek ())
1335 SetMSBuildThisFileProperties (this_file_property_stack.Peek ());
1338 void SetMSBuildThisFileProperties (string full_filename)
1340 if (String.IsNullOrEmpty (full_filename))
1343 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFile", Path.GetFileName (full_filename), PropertyType.Reserved));
1344 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFileFullPath", full_filename, PropertyType.Reserved));
1345 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFileName", Path.GetFileNameWithoutExtension (full_filename), PropertyType.Reserved));
1346 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFileExtension", Path.GetExtension (full_filename), PropertyType.Reserved));
1348 string project_dir = Path.GetDirectoryName (full_filename) + Path.DirectorySeparatorChar;
1349 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFileDirectory", project_dir, PropertyType.Reserved));
1350 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFileDirectoryNoRoot",
1351 project_dir.Substring (Path.GetPathRoot (project_dir).Length),
1352 PropertyType.Reserved));
1356 internal void LogWarning (string filename, string message, params object[] messageArgs)
1358 BuildWarningEventArgs bwea = new BuildWarningEventArgs (
1359 null, null, filename, 0, 0, 0, 0, String.Format (message, messageArgs),
1361 ParentEngine.EventSource.FireWarningRaised (this, bwea);
1364 internal void LogError (string filename, string message,
1365 params object[] messageArgs)
1367 BuildErrorEventArgs beea = new BuildErrorEventArgs (
1368 null, null, filename, 0, 0, 0, 0, String.Format (message, messageArgs),
1370 ParentEngine.EventSource.FireErrorRaised (this, beea);
1373 static string ExtensionsPath {
1375 if (extensions_path == null) {
1376 // NOTE: code from mcs/tools/gacutil/driver.cs
1377 PropertyInfo gac = typeof (System.Environment).GetProperty (
1378 "GacPath", BindingFlags.Static | BindingFlags.NonPublic);
1381 MethodInfo get_gac = gac.GetGetMethod (true);
1382 string gac_path = (string) get_gac.Invoke (null, null);
1383 extensions_path = Path.GetFullPath (Path.Combine (
1384 gac_path, Path.Combine ("..", "xbuild")));
1387 return extensions_path;
1391 public BuildPropertyGroup EvaluatedProperties {
1393 if (needToReevaluate) {
1394 needToReevaluate = false;
1397 return evaluatedProperties;
1401 internal IEnumerable EvaluatedPropertiesAsDictionaryEntries {
1403 foreach (BuildProperty bp in EvaluatedProperties)
1404 yield return new DictionaryEntry (bp.Name, bp.Value);
1408 public string FullFileName {
1409 get { return fullFileName; }
1410 set { fullFileName = value; }
1413 public BuildPropertyGroup GlobalProperties {
1414 get { return globalProperties; }
1417 throw new ArgumentNullException ("value");
1420 throw new InvalidOperationException ("GlobalProperties can not be set to persisted property group.");
1422 globalProperties = value;
1426 public bool IsDirty {
1427 get { return isDirty; }
1430 public bool IsValidated {
1431 get { return isValidated; }
1432 set { isValidated = value; }
1435 public BuildItemGroupCollection ItemGroups {
1436 get { return itemGroups; }
1439 public ImportCollection Imports {
1440 get { return imports; }
1443 public string InitialTargets {
1445 return String.Join ("; ", initialTargets.ToArray ());
1448 initialTargets.Clear ();
1449 xmlDocument.DocumentElement.SetAttribute ("InitialTargets", value);
1451 initialTargets.AddRange (value.Split (
1452 new char [] {';', ' '}, StringSplitOptions.RemoveEmptyEntries));
1456 public Engine ParentEngine {
1457 get { return parentEngine; }
1460 public BuildPropertyGroupCollection PropertyGroups {
1461 get { return propertyGroups; }
1464 public string SchemaFile {
1465 get { return schemaFile; }
1466 set { schemaFile = value; }
1469 public TargetCollection Targets {
1470 get { return targets; }
1473 public DateTime TimeOfLastDirty {
1474 get { return timeOfLastDirty; }
1477 public UsingTaskCollection UsingTasks {
1478 get { return usingTasks; }
1483 get { return xmlDocument.InnerXml; }
1486 // corresponds to the xml attribute
1487 public string DefaultToolsVersion {
1489 if (xmlDocument != null)
1490 return xmlDocument.DocumentElement.GetAttribute ("ToolsVersion");
1494 if (xmlDocument != null)
1495 xmlDocument.DocumentElement.SetAttribute ("ToolsVersion", value);
1499 public bool HasToolsVersionAttribute {
1501 return xmlDocument != null && xmlDocument.DocumentElement.HasAttribute ("ToolsVersion");
1505 public string ToolsVersion {
1509 internal Dictionary <string, BuildItemGroup> LastItemGroupContaining {
1510 get { return last_item_group_containing; }
1513 internal static XmlNamespaceManager XmlNamespaceManager {
1515 if (manager == null) {
1516 manager = new XmlNamespaceManager (new NameTable ());
1517 manager.AddNamespace ("tns", ns);
1524 internal TaskDatabase TaskDatabase {
1525 get { return taskDatabase; }
1528 internal XmlDocument XmlDocument {
1529 get { return xmlDocument; }
1532 internal static string XmlNamespace {
1538 PlatformID pid = Environment.OSVersion.Platform;
1546 return "Windows_NT";