[xbuild] Use the env var $MSBuildExtensionsPath before trying other paths.
[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 // 
7 // (C) 2006 Marek Sieradzki
8 //
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:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
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.
27
28 #if NET_2_0
29
30 using System;
31 using System.IO;
32 using System.Xml;
33
34 using Microsoft.Build.Framework;
35 using Mono.XBuild.Utilities;
36
37 namespace Microsoft.Build.BuildEngine {
38         public class Import {
39                 XmlElement      importElement;
40                 Project         project;
41                 ImportedProject originalProject;
42                 string          evaluatedProjectPath;
43
44                 static string DotConfigExtensionsPath = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData),
45                                                                 Path.Combine ("xbuild", "tasks"));
46                 const string MacOSXExternalXBuildDir = "/Library/Frameworks/Mono.framework/External/xbuild";
47         
48                 internal Import (XmlElement importElement, Project project, ImportedProject originalProject)
49                 {
50                         if (importElement == null)
51                                 throw new ArgumentNullException ("importElement");
52                         if (project == null)
53                                 throw new ArgumentNullException ("project");
54                 
55                         this.project = project;
56                         this.importElement = importElement;
57                         this.originalProject = originalProject;
58
59                         if (ProjectPath == String.Empty)
60                                 throw new InvalidProjectFileException ("The required attribute \"Project\" is missing from element <Import>.");
61
62                         if (ConditionParser.ParseAndEvaluate (Condition, project)) {
63                                 evaluatedProjectPath = EvaluateProjectPath (ProjectPath);
64                                 evaluatedProjectPath = GetFullPath ();
65                                 if (EvaluatedProjectPath == String.Empty)
66                                         throw new InvalidProjectFileException ("The required attribute \"Project\" is missing from element <Import>.");
67                         }
68                 }
69
70                 // FIXME: condition
71                 internal void Evaluate (bool ignoreMissingImports)
72                 {
73                         string filename = evaluatedProjectPath;
74                         // NOTE: it's a hack to transform Microsoft.CSharp.Targets to Microsoft.CSharp.targets
75                         if (Path.HasExtension (filename))
76                                 filename = Path.ChangeExtension (filename, Path.GetExtension (filename));
77                         
78                         if (!File.Exists (filename)) {
79                                 if (ignoreMissingImports) {
80                                         project.LogWarning (project.FullFileName, "Could not find project file {0}, to import. Ignoring.", filename);
81                                         return;
82                                 } else {
83                                         throw new InvalidProjectFileException (String.Format ("Imported project: \"{0}\" does not exist.", filename));
84                                 }
85                         }
86                         
87                         ImportedProject importedProject = new ImportedProject ();
88                         importedProject.Load (filename);
89
90                         project.ProcessElements (importedProject.XmlDocument.DocumentElement, importedProject);
91                 }
92
93                 string EvaluateProjectPath (string file)
94                 {
95                         string ret;
96                         if (EvaluateAsMSBuildExtensionsPath (file, "MSBuildExtensionsPath", out ret) ||
97                                 EvaluateAsMSBuildExtensionsPath (file, "MSBuildExtensionsPath32", out ret) ||
98                                 EvaluateAsMSBuildExtensionsPath (file, "MSBuildExtensionsPath64", out ret))
99                                 return ret;
100
101                         return EvaluatePath (file);
102                 }
103
104                 bool EvaluateAsMSBuildExtensionsPath (string file, string property_name, out string epath)
105                 {
106                         epath = null;
107                         string property_ref = String.Format ("$({0})", property_name);
108                         if (file.IndexOf (property_ref) < 0)
109                                 return false;
110
111                         // This is a *HACK* to support multiple paths for
112                         // MSBuildExtensionsPath property. Normally it would
113                         // get resolved to a single value, but here we special
114                         // case it and try ~/.config/xbuild/tasks and any
115                         // paths specified in the env var $MSBuildExtensionsPath .
116                         //
117                         // The property itself will resolve to the default
118                         // location though, so you get in any other part of the
119                         // project.
120
121                         string envvar = Environment.GetEnvironmentVariable (property_name);
122                         envvar = String.Join (":", new string [] {
123                                                 (envvar ?? String.Empty),
124                                                 // For mac osx, look in the 'External' dir on macosx,
125                                                 // see bug #663180
126                                                 MSBuildUtils.RunningOnMac ? MacOSXExternalXBuildDir : String.Empty,
127                                                 DotConfigExtensionsPath});
128
129                         string [] paths = envvar.Split (new char [] {':'}, StringSplitOptions.RemoveEmptyEntries);
130                         foreach (string path in paths) {
131                                 if (!Directory.Exists (path)) {
132                                         project.ParentEngine.LogMessage (MessageImportance.Low, "Extension path '{0}' not found, ignoring.", path);
133                                         continue;
134                                 }
135
136                                 string pfile = Path.GetFullPath (file.Replace ("\\", "/").Replace (
137                                                         property_ref, path + Path.DirectorySeparatorChar));
138
139                                 var evaluated_path = EvaluatePath (pfile);
140                                 if (File.Exists (evaluated_path)) {
141                                         project.ParentEngine.LogMessage (MessageImportance.Low,
142                                                 "{0}: Importing project {1} from extension path {2}", project.FullFileName, evaluated_path, path);
143                                         epath = pfile;
144                                         return true;
145                                 }
146                                 project.ParentEngine.LogMessage (MessageImportance.Low,
147                                                 "{0}: Couldn't find project {1} for extension path {2}", project.FullFileName, evaluated_path, path);
148                         }
149
150                         return false;
151                 }
152
153                 string EvaluatePath (string path)
154                 {
155                         var exp = new Expression ();
156                         exp.Parse (path, ParseOptions.Split);
157                         return (string) exp.ConvertTo (project, typeof (string));
158                 }
159
160                 string GetFullPath ()
161                 {
162                         string file = EvaluatedProjectPath;
163
164                         if (!Path.IsPathRooted (EvaluatedProjectPath)) {
165                                 string dir = null;
166                                 if (originalProject == null) {
167                                         if (project.FullFileName != String.Empty) // Path.GetDirectoryName throws exception on String.Empty
168                                                 dir = Path.GetDirectoryName (project.FullFileName);
169                                 } else {
170                                         if (originalProject.FullFileName != String.Empty)
171                                                 dir = Path.GetDirectoryName (originalProject.FullFileName);
172                                 }
173                                 if (dir != null)
174                                         file = Path.Combine (dir, EvaluatedProjectPath);
175                         }
176                         
177                         return MSBuildUtils.FromMSBuildPath (file);
178                 }
179                 
180                 public string Condition {
181                         get {
182                                 string s = importElement.GetAttribute ("Condition");
183                                 return s == String.Empty ? null : s;
184                         }
185                 }
186                 
187                 public string EvaluatedProjectPath {
188                         get { return evaluatedProjectPath; }
189                 }
190                 
191                 public bool IsImported {
192                         get { return originalProject != null; }
193                 }
194                 
195                 public string ProjectPath {
196                         get { return importElement.GetAttribute ("Project"); }
197                 }
198         }
199 }
200
201 #endif