2 // Import.cs: Represents a single Import element in an MSBuild project.
5 // Marek Sieradzki (marek.sieradzki@gmail.com)
6 // Ankit Jain (jankit@novell.com)
8 // (C) 2006 Marek Sieradzki
9 // Copyright 2011 Novell, Inc (http://www.novell.com)
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:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
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.
33 using System.Collections.Generic;
38 using Microsoft.Build.Framework;
39 using Microsoft.Build.Utilities;
40 using Mono.XBuild.Utilities;
42 namespace Microsoft.Build.BuildEngine {
44 XmlElement importElement;
46 ImportedProject originalProject;
47 string evaluatedProjectPath;
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 ();
54 internal Import (XmlElement importElement, Project project, ImportedProject originalProject)
55 : this (importElement, null, project, originalProject)
58 // if @alternateProjectPath is available then that it used as the EvaluatedProjectPath!
59 internal Import (XmlElement importElement, string alternateProjectPath, Project project, ImportedProject originalProject)
61 if (importElement == null)
62 throw new ArgumentNullException ("importElement");
64 throw new ArgumentNullException ("project");
66 this.project = project;
67 this.importElement = importElement;
68 this.originalProject = originalProject;
70 if (ProjectPath == String.Empty)
71 throw new InvalidProjectFileException ("The required attribute \"Project\" is missing from element <Import>.");
73 if (ConditionParser.ParseAndEvaluate (Condition, project)) {
74 evaluatedProjectPath = String.IsNullOrEmpty (alternateProjectPath) ? EvaluateProjectPath (ProjectPath) : alternateProjectPath;
76 evaluatedProjectPath = GetFullPath ();
77 if (EvaluatedProjectPath == String.Empty)
78 throw new InvalidProjectFileException ("The required attribute \"Project\" is missing from element <Import>.");
82 internal bool CheckEvaluatedProjectPathExists ()
84 string path = EvaluatedProjectPath;
86 if (File.Exists (path))
89 if (Path.GetFileName (path) == "Microsoft.CSharp.Targets") {
90 path = Path.ChangeExtension (path, ".targets");
91 if (File.Exists (path))
99 internal void Evaluate (bool ignoreMissingImports)
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");
106 if (!File.Exists (filename)) {
107 if (ignoreMissingImports) {
108 project.LogWarning (project.FullFileName, "Could not find project file {0}, to import. Ignoring.", filename);
111 throw new InvalidProjectFileException (String.Format ("Imported project: \"{0}\" does not exist.", filename));
115 ImportedProject importedProject = new ImportedProject ();
116 importedProject.Load (filename);
118 project.ProcessElements (importedProject.XmlDocument.DocumentElement, importedProject);
121 string EvaluateProjectPath (string file)
123 return Expression.ParseAs<string> (file, ParseOptions.Split, project);
126 string GetFullPath ()
128 string file = EvaluatedProjectPath;
129 if (!Path.IsPathRooted (file) && !String.IsNullOrEmpty (ContainedInProjectFileName))
130 file = Path.Combine (Path.GetDirectoryName (ContainedInProjectFileName), file);
132 return MSBuildUtils.FromMSBuildPath (file);
135 // For every extension path, in order, finds suitable
136 // import filename(s) matching the Import, and calls
139 // func: bool func(importPath, from_source_msg)
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
147 internal static void ForEachExtensionPathTillFound (XmlElement xmlElement, Project project, ImportedProject importingProject,
148 Func<string, string, bool> func)
150 string project_attribute = xmlElement.GetAttribute ("Project");
151 string condition_attribute = xmlElement.GetAttribute ("Condition");
153 bool has_extn_ref = project_attribute.IndexOf ("$(MSBuildExtensionsPath)") >= 0 ||
154 project_attribute.IndexOf ("$(MSBuildExtensionsPath32)") >= 0 ||
155 project_attribute.IndexOf ("$(MSBuildExtensionsPath64)") >= 0;
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));
162 base_dir_info = new DirectoryInfo (Directory.GetCurrentDirectory ());
164 IEnumerable<string> extn_paths = has_extn_ref ? GetExtensionPaths (project) : new string [] {null};
165 bool import_needed = false;
168 foreach (string path in extn_paths) {
169 string extn_msg = null;
171 project.SetExtensionsPathProperties (path);
172 extn_msg = "from extension path " + path;
175 // do this after setting new Extension properties, as condition might
177 if (!ConditionParser.ParseAndEvaluate (condition_attribute, project))
180 import_needed = true;
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)) {
187 if (func (importPath, extn_msg))
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);
201 project.SetExtensionsPathProperties (Project.DefaultExtensionsPath);
205 throw new InvalidProjectFileException (String.Format ("{0} could not import \"{1}\"", importingFile, project_attribute));
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)
213 string parsed_import = Expression.ParseAs<string> (import_string, ParseOptions.AllowItemsNoMetadataAndSplit, project);
214 if (parsed_import != null)
215 parsed_import = parsed_import.Trim ();
217 if (String.IsNullOrEmpty (parsed_import))
218 throw new InvalidProjectFileException ("The required attribute \"Project\" in Import is empty");
221 if (DirectoryScanner.HasWildcard (parsed_import)) {
222 var directoryScanner = new DirectoryScanner () {
223 Includes = new ITaskItem [] { new TaskItem (parsed_import) },
224 BaseDirectory = base_dir_info
226 directoryScanner.Scan ();
228 foreach (ITaskItem matchedItem in directoryScanner.MatchedItems)
229 yield return matchedItem.ItemSpec;
232 yield return parsed_import;
235 // Gives a list of extensions paths to try for $(MSBuildExtensionsPath),
237 static IEnumerable<string> GetExtensionPaths (Project project)
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
244 // The property itself will resolve to the default
245 // location though, so you get that in any other part of the
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,
253 MSBuildUtils.RunningOnMac ? MacOSXExternalXBuildDir : String.Empty,
254 DotConfigExtensionsPath,
255 Project.DefaultExtensionsPath});
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))
262 if (!Directory.Exists (extn_path)) {
263 project.ParentEngine.LogMessage (MessageImportance.Low, "Extension path '{0}' not found, ignoring.", extn_path);
267 pathsTable [extn_path] = extn_path;
268 yield return extn_path;
272 public string Condition {
274 string s = importElement.GetAttribute ("Condition");
275 return s == String.Empty ? null : s;
279 public string EvaluatedProjectPath {
280 get { return evaluatedProjectPath; }
283 public bool IsImported {
284 get { return originalProject != null; }
287 public string ProjectPath {
288 get { return importElement.GetAttribute ("Project"); }
291 internal string ContainedInProjectFileName {
292 get { return originalProject != null ? originalProject.FullFileName : project.FullFileName; }