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