f9c0e52bbc1439d29e50e28237bcb40d84a52c76
[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                 // FIXME: condition
83                 internal void Evaluate (bool ignoreMissingImports)
84                 {
85                         string filename = evaluatedProjectPath;
86                         // NOTE: it's a hack to transform Microsoft.CSharp.Targets to Microsoft.CSharp.targets
87                         if (!File.Exists (filename) && Path.GetFileName (filename) == "Microsoft.CSharp.Targets")
88                                 filename = Path.ChangeExtension (filename, ".targets");
89
90                         if (!File.Exists (filename)) {
91                                 if (ignoreMissingImports) {
92                                         project.LogWarning (project.FullFileName, "Could not find project file {0}, to import. Ignoring.", filename);
93                                         return;
94                                 } else {
95                                         throw new InvalidProjectFileException (String.Format ("Imported project: \"{0}\" does not exist.", filename));
96                                 }
97                         }
98                         
99                         ImportedProject importedProject = new ImportedProject ();
100                         importedProject.Load (filename);
101
102                         project.ProcessElements (importedProject.XmlDocument.DocumentElement, importedProject);
103                 }
104
105                 string EvaluateProjectPath (string file)
106                 {
107                         return Expression.ParseAs<string> (file, ParseOptions.Split, project);
108                 }
109
110                 string GetFullPath ()
111                 {
112                         string file = EvaluatedProjectPath;
113                         if (!Path.IsPathRooted (file) && !String.IsNullOrEmpty (ContainedInProjectFileName))
114                                 file = Path.Combine (Path.GetDirectoryName (ContainedInProjectFileName), file);
115
116                         return MSBuildUtils.FromMSBuildPath (file);
117                 }
118
119                 // For every extension path, in order, finds suitable
120                 // import filename(s) matching the Import, and calls
121                 // @func with them
122                 //
123                 // func: bool func(importPath, from_source_msg)
124                 //
125                 // If for an extension path, atleast one file gets imported,
126                 // then it stops at that.
127                 // So, in case imports like "$(MSBuildExtensionsPath)\foo\*",
128                 // for every extension path, it will try to import the "foo\*",
129                 // and if atleast one file gets successfully imported, then it
130                 // stops at that
131                 internal static void ForEachExtensionPathTillFound (XmlElement xmlElement, Project project, ImportedProject importingProject,
132                                 Func<string, string, bool> func)
133                 {
134                         string project_attribute = xmlElement.GetAttribute ("Project");
135                         string condition_attribute = xmlElement.GetAttribute ("Condition");
136
137                         bool has_extn_ref = project_attribute.IndexOf ("$(MSBuildExtensionsPath)") >= 0 ||
138                                                 project_attribute.IndexOf ("$(MSBuildExtensionsPath32)") >= 0 ||
139                                                 project_attribute.IndexOf ("$(MSBuildExtensionsPath64)") >= 0;
140
141                         string importingFile = importingProject != null ? importingProject.FullFileName : project.FullFileName;
142                         DirectoryInfo base_dir_info = null;
143                         if (!String.IsNullOrEmpty (importingFile))
144                                 base_dir_info = new DirectoryInfo (Path.GetDirectoryName (importingFile));
145                         else
146                                 base_dir_info = new DirectoryInfo (Directory.GetCurrentDirectory ());
147
148                         IEnumerable<string> extn_paths = has_extn_ref ? GetExtensionPaths (project) : new string [] {null};
149                         try {
150                                 foreach (string path in extn_paths) {
151                                         string extn_msg = null;
152                                         if (has_extn_ref) {
153                                                 project.SetExtensionsPathProperties (path);
154                                                 extn_msg = "from extension path " + path;
155                                         }
156
157                                         // do this after setting new Extension properties, as condition might
158                                         // reference it
159                                         if (!ConditionParser.ParseAndEvaluate (condition_attribute, project)) {
160                                                 project.ParentEngine.LogMessage (MessageImportance.Low,
161                                                                 "{0}: Not importing '{1}' project as the condition '{2}' is false",
162                                                                 importingFile, project_attribute, condition_attribute);
163                                                 continue;
164                                         }
165
166                                         // We stop if atleast one file got imported.
167                                         // Remaining extension paths are *not* tried
168                                         bool atleast_one = false;
169                                         foreach (string importPath in GetImportPathsFromString (project_attribute, project, base_dir_info)) {
170                                                 try {
171                                                         if (func (importPath, extn_msg))
172                                                                 atleast_one = true;
173                                                 } catch (Exception e) {
174                                                         throw new InvalidProjectFileException (String.Format (
175                                                                                 "{0}: Project file could not be imported, it was being imported by " +
176                                                                                 "{1}: {2}", importPath, importingFile, e.Message), e);
177                                                 }
178                                         }
179
180                                         if (atleast_one)
181                                                 return;
182                                 }
183                         } finally {
184                                 if (has_extn_ref)
185                                         project.SetExtensionsPathProperties (Project.DefaultExtensionsPath);
186                         }
187                 }
188
189                 // Parses the Project attribute from an Import,
190                 // and returns the import filenames that match.
191                 // This handles wildcards also
192                 static IEnumerable<string> GetImportPathsFromString (string import_string, Project project, DirectoryInfo base_dir_info)
193                 {
194                         string parsed_import = Expression.ParseAs<string> (import_string, ParseOptions.AllowItemsNoMetadataAndSplit, project);
195                         if (parsed_import != null)
196                                 parsed_import = parsed_import.Trim ();
197
198                         if (String.IsNullOrEmpty (parsed_import))
199                                 throw new InvalidProjectFileException ("The required attribute \"Project\" in Import is empty");
200
201 #if NET_4_0
202                         if (DirectoryScanner.HasWildcard (parsed_import)) {
203                                 var directoryScanner = new DirectoryScanner () {
204                                         Includes = new ITaskItem [] { new TaskItem (parsed_import) },
205                                         BaseDirectory = base_dir_info
206                                 };
207                                 directoryScanner.Scan ();
208
209                                 foreach (ITaskItem matchedItem in directoryScanner.MatchedItems)
210                                         yield return matchedItem.ItemSpec;
211                         } else
212 #endif
213                                 yield return parsed_import;
214                 }
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
229                         string envvar = Environment.GetEnvironmentVariable ("MSBuildExtensionsPath");
230                         envvar = String.Join (PathSeparatorAsString, new string [] {
231                                                 (envvar ?? String.Empty),
232                                                 // For mac osx, look in the 'External' dir on macosx,
233                                                 // see bug #663180
234                                                 MSBuildUtils.RunningOnMac ? MacOSXExternalXBuildDir : String.Empty,
235                                                 DotConfigExtensionsPath,
236                                                 Project.DefaultExtensionsPath});
237
238                         var pathsTable = new Dictionary<string, string> ();
239                         foreach (string extn_path in envvar.Split (new char [] {Path.PathSeparator}, StringSplitOptions.RemoveEmptyEntries)) {
240                                 if (pathsTable.ContainsKey (extn_path))
241                                         continue;
242
243                                 if (!Directory.Exists (extn_path)) {
244                                         project.ParentEngine.LogMessage (MessageImportance.Low, "Extension path '{0}' not found, ignoring.", extn_path);
245                                         continue;
246                                 }
247
248                                 pathsTable [extn_path] = extn_path;
249                                 yield return extn_path;
250                         }
251                 }
252
253                 public string Condition {
254                         get {
255                                 string s = importElement.GetAttribute ("Condition");
256                                 return s == String.Empty ? null : s;
257                         }
258                 }
259                 
260                 public string EvaluatedProjectPath {
261                         get { return evaluatedProjectPath; }
262                 }
263                 
264                 public bool IsImported {
265                         get { return originalProject != null; }
266                 }
267                 
268                 public string ProjectPath {
269                         get { return importElement.GetAttribute ("Project"); }
270                 }
271
272                 internal string ContainedInProjectFileName {
273                         get { return originalProject != null ? originalProject.FullFileName : project.FullFileName; }
274                 }
275         }
276 }
277
278 #endif