PageRenderTime 46ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/mcs/class/Microsoft.Build.Engine/Microsoft.Build.BuildEngine/Import.cs

https://github.com/pruiz/mono
C# | 320 lines | 207 code | 49 blank | 64 comment | 44 complexity | 66474eec8c5b77e261022ee367803fd3 MD5 | raw file
Possible License(s): LGPL-2.0, MPL-2.0-no-copyleft-exception, CC-BY-SA-3.0, GPL-2.0
  1. //
  2. // Import.cs: Represents a single Import element in an MSBuild project.
  3. //
  4. // Author:
  5. // Marek Sieradzki (marek.sieradzki@gmail.com)
  6. // Ankit Jain (jankit@novell.com)
  7. //
  8. // (C) 2006 Marek Sieradzki
  9. // Copyright 2011 Novell, Inc (http://www.novell.com)
  10. //
  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:
  18. //
  19. // The above copyright notice and this permission notice shall be
  20. // included in all copies or substantial portions of the Software.
  21. //
  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.
  29. using System;
  30. using System.Collections.Generic;
  31. using System.IO;
  32. using System.Linq;
  33. using System.Xml;
  34. using Microsoft.Build.Framework;
  35. using Microsoft.Build.Utilities;
  36. using Mono.XBuild.Utilities;
  37. namespace Microsoft.Build.BuildEngine {
  38. public class Import {
  39. XmlElement importElement;
  40. Project project;
  41. ImportedProject originalProject;
  42. string evaluatedProjectPath;
  43. static string DotConfigExtensionsPath = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData),
  44. Path.Combine ("xbuild", "tasks"));
  45. const string MacOSXExternalXBuildDir = "/Library/Frameworks/Mono.framework/External/xbuild";
  46. static string PathSeparatorAsString = Path.PathSeparator.ToString ();
  47. internal Import (XmlElement importElement, Project project, ImportedProject originalProject)
  48. : this (importElement, null, project, originalProject)
  49. {}
  50. // if @alternateProjectPath is available then that it used as the EvaluatedProjectPath!
  51. internal Import (XmlElement importElement, string alternateProjectPath, Project project, ImportedProject originalProject)
  52. {
  53. if (importElement == null)
  54. throw new ArgumentNullException ("importElement");
  55. if (project == null)
  56. throw new ArgumentNullException ("project");
  57. this.project = project;
  58. this.importElement = importElement;
  59. this.originalProject = originalProject;
  60. if (ProjectPath == String.Empty)
  61. throw new InvalidProjectFileException ("The required attribute \"Project\" is missing from element <Import>.");
  62. if (ConditionParser.ParseAndEvaluate (Condition, project)) {
  63. evaluatedProjectPath = String.IsNullOrEmpty (alternateProjectPath) ? EvaluateProjectPath (ProjectPath) : alternateProjectPath;
  64. evaluatedProjectPath = GetFullPath ();
  65. if (EvaluatedProjectPath == String.Empty)
  66. throw new InvalidProjectFileException ("The required attribute \"Project\" is missing from element <Import>.");
  67. }
  68. }
  69. internal bool CheckEvaluatedProjectPathExists ()
  70. {
  71. string path = EvaluatedProjectPath;
  72. if (File.Exists (path))
  73. return true;
  74. if (Path.GetFileName (path) == "Microsoft.CSharp.Targets") {
  75. path = Path.ChangeExtension (path, ".targets");
  76. if (File.Exists (path))
  77. return true;
  78. }
  79. return false;
  80. }
  81. // FIXME: condition
  82. internal void Evaluate (bool ignoreMissingImports)
  83. {
  84. string filename = evaluatedProjectPath;
  85. // NOTE: it's a hack to transform Microsoft.CSharp.Targets to Microsoft.CSharp.targets
  86. if (!File.Exists (filename) && Path.GetFileName (filename) == "Microsoft.CSharp.Targets")
  87. filename = Path.ChangeExtension (filename, ".targets");
  88. if (!File.Exists (filename)) {
  89. if (ignoreMissingImports) {
  90. project.LogWarning (project.FullFileName, "Could not find project file {0}, to import. Ignoring.", filename);
  91. return;
  92. } else {
  93. throw new InvalidProjectFileException (String.Format ("Imported project: \"{0}\" does not exist.", filename));
  94. }
  95. }
  96. ImportedProject importedProject = new ImportedProject ();
  97. importedProject.Load (filename);
  98. project.ProcessElements (importedProject.XmlDocument.DocumentElement, importedProject);
  99. }
  100. string EvaluateProjectPath (string file)
  101. {
  102. return Expression.ParseAs<string> (file, ParseOptions.Split, project);
  103. }
  104. string GetFullPath ()
  105. {
  106. string file = EvaluatedProjectPath;
  107. if (!Path.IsPathRooted (file) && !String.IsNullOrEmpty (ContainedInProjectFileName))
  108. file = Path.Combine (Path.GetDirectoryName (ContainedInProjectFileName), file);
  109. return MSBuildUtils.FromMSBuildPath (file);
  110. }
  111. // For every extension path, in order, finds suitable
  112. // import filename(s) matching the Import, and calls
  113. // @func with them
  114. //
  115. // func: bool func(importPath, from_source_msg)
  116. //
  117. // If for an extension path, atleast one file gets imported,
  118. // then it stops at that.
  119. // So, in case imports like "$(MSBuildExtensionsPath)\foo\*",
  120. // for every extension path, it will try to import the "foo\*",
  121. // and if atleast one file gets successfully imported, then it
  122. // stops at that
  123. internal static void ForEachExtensionPathTillFound (XmlElement xmlElement, Project project, ImportedProject importingProject,
  124. Func<string, string, bool> func)
  125. {
  126. string project_attribute = xmlElement.GetAttribute ("Project");
  127. string condition_attribute = xmlElement.GetAttribute ("Condition");
  128. bool has_extn_ref = project_attribute.IndexOf ("$(MSBuildExtensionsPath)") >= 0 ||
  129. project_attribute.IndexOf ("$(MSBuildExtensionsPath32)") >= 0 ||
  130. project_attribute.IndexOf ("$(MSBuildExtensionsPath64)") >= 0;
  131. bool condn_has_extn_ref = condition_attribute.IndexOf ("$(MSBuildExtensionsPath)") >= 0 ||
  132. condition_attribute.IndexOf ("$(MSBuildExtensionsPath32)") >= 0 ||
  133. condition_attribute.IndexOf ("$(MSBuildExtensionsPath64)") >= 0;
  134. // we can skip the following logic in case the condition doesn't reference any extension paths
  135. // and it evaluates to false since nothing would change anyway
  136. if (!condn_has_extn_ref && !ConditionParser.ParseAndEvaluate (condition_attribute, project))
  137. return;
  138. string importingFile = importingProject != null ? importingProject.FullFileName : project.FullFileName;
  139. DirectoryInfo base_dir_info = null;
  140. if (!String.IsNullOrEmpty (importingFile))
  141. base_dir_info = new DirectoryInfo (Path.GetDirectoryName (importingFile));
  142. else
  143. base_dir_info = new DirectoryInfo (Directory.GetCurrentDirectory ());
  144. var importPaths = GetImportPathsFromString (project_attribute, project, base_dir_info);
  145. var extensionPaths = GetExtensionPaths (project);
  146. if (!has_extn_ref) {
  147. foreach (var importPath in importPaths) {
  148. foreach (var extensionPath in extensionPaths) {
  149. has_extn_ref = has_extn_ref || importPath.IndexOf (extensionPath) >= 0;
  150. }
  151. }
  152. }
  153. IEnumerable<string> extn_paths = has_extn_ref ? extensionPaths : new string [] { null };
  154. bool import_needed = false;
  155. var currentLoadSettings = project.ProjectLoadSettings;
  156. try {
  157. foreach (var settings in new ProjectLoadSettings [] { ProjectLoadSettings.None, currentLoadSettings }) {
  158. foreach (string path in extn_paths) {
  159. string extn_msg = null;
  160. if (has_extn_ref) {
  161. project.SetExtensionsPathProperties (path);
  162. extn_msg = "from extension path " + path;
  163. }
  164. // do this after setting new Extension properties, as condition might
  165. // reference it
  166. if (!ConditionParser.ParseAndEvaluate (condition_attribute, project))
  167. continue;
  168. import_needed = true;
  169. project.ProjectLoadSettings = settings;
  170. // We stop if atleast one file got imported.
  171. // Remaining extension paths are *not* tried
  172. bool atleast_one = false;
  173. foreach (string importPath in importPaths) {
  174. try {
  175. if (func (importPath, extn_msg))
  176. atleast_one = true;
  177. } catch (Exception e) {
  178. throw new InvalidProjectFileException (String.Format (
  179. "{0}: Project file could not be imported, it was being imported by " +
  180. "{1}: {2}", importPath, importingFile, e.Message), e);
  181. }
  182. }
  183. if (atleast_one)
  184. return;
  185. }
  186. }
  187. } finally {
  188. project.ProjectLoadSettings = currentLoadSettings;
  189. if (has_extn_ref)
  190. project.SetExtensionsPathProperties (Project.DefaultExtensionsPath);
  191. }
  192. if (import_needed)
  193. throw new InvalidProjectFileException (String.Format ("{0} could not import \"{1}\"", importingFile, project_attribute));
  194. }
  195. // Parses the Project attribute from an Import,
  196. // and returns the import filenames that match.
  197. // This handles wildcards also
  198. static IEnumerable<string> GetImportPathsFromString (string import_string, Project project, DirectoryInfo base_dir_info)
  199. {
  200. string parsed_import = Expression.ParseAs<string> (import_string, ParseOptions.AllowItemsNoMetadataAndSplit, project);
  201. if (parsed_import != null)
  202. parsed_import = parsed_import.Trim ();
  203. if (String.IsNullOrEmpty (parsed_import))
  204. throw new InvalidProjectFileException ("The required attribute \"Project\" in Import is empty");
  205. if (DirectoryScanner.HasWildcard (parsed_import)) {
  206. var directoryScanner = new DirectoryScanner () {
  207. Includes = new ITaskItem [] { new TaskItem (parsed_import) },
  208. BaseDirectory = base_dir_info
  209. };
  210. directoryScanner.Scan ();
  211. foreach (ITaskItem matchedItem in directoryScanner.MatchedItems)
  212. yield return matchedItem.ItemSpec;
  213. } else
  214. yield return parsed_import;
  215. }
  216. // Gives a list of extensions paths to try for $(MSBuildExtensionsPath),
  217. // *in-order*
  218. static IEnumerable<string> GetExtensionPaths (Project project)
  219. {
  220. // This is a *HACK* to support multiple paths for
  221. // MSBuildExtensionsPath property. Normally it would
  222. // get resolved to a single value, but here we special
  223. // case it and try various paths, see the code below
  224. //
  225. // The property itself will resolve to the default
  226. // location though, so you get that in any other part of the
  227. // project.
  228. string envvar = Environment.GetEnvironmentVariable ("MSBuildExtensionsPath");
  229. envvar = String.Join (PathSeparatorAsString, new string [] {
  230. (envvar ?? String.Empty),
  231. // For mac osx, look in the 'External' dir on macosx,
  232. // see bug #663180
  233. MSBuildUtils.RunningOnMac ? MacOSXExternalXBuildDir : String.Empty,
  234. DotConfigExtensionsPath,
  235. Project.DefaultExtensionsPath});
  236. var pathsTable = new Dictionary<string, string> ();
  237. foreach (string extn_path in envvar.Split (new char [] {Path.PathSeparator}, StringSplitOptions.RemoveEmptyEntries)) {
  238. if (pathsTable.ContainsKey (extn_path))
  239. continue;
  240. if (!Directory.Exists (extn_path)) {
  241. if (extn_path != DotConfigExtensionsPath)
  242. project.ParentEngine.LogMessage (
  243. MessageImportance.Low,
  244. "Extension path '{0}' not found, ignoring.",
  245. extn_path);
  246. continue;
  247. }
  248. pathsTable [extn_path] = extn_path;
  249. yield return extn_path;
  250. }
  251. }
  252. public string Condition {
  253. get {
  254. string s = importElement.GetAttribute ("Condition");
  255. return s == String.Empty ? null : s;
  256. }
  257. }
  258. public string EvaluatedProjectPath {
  259. get { return evaluatedProjectPath; }
  260. }
  261. public bool IsImported {
  262. get { return originalProject != null; }
  263. }
  264. public string ProjectPath {
  265. get { return importElement.GetAttribute ("Project"); }
  266. }
  267. internal string ContainedInProjectFileName {
  268. get { return originalProject != null ? originalProject.FullFileName : project.FullFileName; }
  269. }
  270. }
  271. }