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