Jo Shields 3c1f479b9d Imported Upstream version 4.0.0~alpha1
Former-commit-id: 806294f5ded97629b74c85c09952f2a74fe182d9
2015-04-07 09:35:12 +01:00

321 lines
12 KiB
C#

//
// Import.cs: Represents a single Import element in an MSBuild project.
//
// Author:
// Marek Sieradzki (marek.sieradzki@gmail.com)
// Ankit Jain (jankit@novell.com)
//
// (C) 2006 Marek Sieradzki
// Copyright 2011 Novell, Inc (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Mono.XBuild.Utilities;
namespace Microsoft.Build.BuildEngine {
public class Import {
XmlElement importElement;
Project project;
ImportedProject originalProject;
string evaluatedProjectPath;
static string DotConfigExtensionsPath = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData),
Path.Combine ("xbuild", "tasks"));
const string MacOSXExternalXBuildDir = "/Library/Frameworks/Mono.framework/External/xbuild";
static string PathSeparatorAsString = Path.PathSeparator.ToString ();
internal Import (XmlElement importElement, Project project, ImportedProject originalProject)
: this (importElement, null, project, originalProject)
{}
// if @alternateProjectPath is available then that it used as the EvaluatedProjectPath!
internal Import (XmlElement importElement, string alternateProjectPath, Project project, ImportedProject originalProject)
{
if (importElement == null)
throw new ArgumentNullException ("importElement");
if (project == null)
throw new ArgumentNullException ("project");
this.project = project;
this.importElement = importElement;
this.originalProject = originalProject;
if (ProjectPath == String.Empty)
throw new InvalidProjectFileException ("The required attribute \"Project\" is missing from element <Import>.");
if (ConditionParser.ParseAndEvaluate (Condition, project)) {
evaluatedProjectPath = String.IsNullOrEmpty (alternateProjectPath) ? EvaluateProjectPath (ProjectPath) : alternateProjectPath;
evaluatedProjectPath = GetFullPath ();
if (EvaluatedProjectPath == String.Empty)
throw new InvalidProjectFileException ("The required attribute \"Project\" is missing from element <Import>.");
}
}
internal bool CheckEvaluatedProjectPathExists ()
{
string path = EvaluatedProjectPath;
if (File.Exists (path))
return true;
if (Path.GetFileName (path) == "Microsoft.CSharp.Targets") {
path = Path.ChangeExtension (path, ".targets");
if (File.Exists (path))
return true;
}
return false;
}
// FIXME: condition
internal void Evaluate (bool ignoreMissingImports)
{
string filename = evaluatedProjectPath;
// NOTE: it's a hack to transform Microsoft.CSharp.Targets to Microsoft.CSharp.targets
if (!File.Exists (filename) && Path.GetFileName (filename) == "Microsoft.CSharp.Targets")
filename = Path.ChangeExtension (filename, ".targets");
if (!File.Exists (filename)) {
if (ignoreMissingImports) {
project.LogWarning (project.FullFileName, "Could not find project file {0}, to import. Ignoring.", filename);
return;
} else {
throw new InvalidProjectFileException (String.Format ("Imported project: \"{0}\" does not exist.", filename));
}
}
ImportedProject importedProject = new ImportedProject ();
importedProject.Load (filename);
project.ProcessElements (importedProject.XmlDocument.DocumentElement, importedProject);
}
string EvaluateProjectPath (string file)
{
return Expression.ParseAs<string> (file, ParseOptions.Split, project);
}
string GetFullPath ()
{
string file = EvaluatedProjectPath;
if (!Path.IsPathRooted (file) && !String.IsNullOrEmpty (ContainedInProjectFileName))
file = Path.Combine (Path.GetDirectoryName (ContainedInProjectFileName), file);
return MSBuildUtils.FromMSBuildPath (file);
}
// For every extension path, in order, finds suitable
// import filename(s) matching the Import, and calls
// @func with them
//
// func: bool func(importPath, from_source_msg)
//
// If for an extension path, atleast one file gets imported,
// then it stops at that.
// So, in case imports like "$(MSBuildExtensionsPath)\foo\*",
// for every extension path, it will try to import the "foo\*",
// and if atleast one file gets successfully imported, then it
// stops at that
internal static void ForEachExtensionPathTillFound (XmlElement xmlElement, Project project, ImportedProject importingProject,
Func<string, string, bool> func)
{
string project_attribute = xmlElement.GetAttribute ("Project");
string condition_attribute = xmlElement.GetAttribute ("Condition");
bool has_extn_ref = project_attribute.IndexOf ("$(MSBuildExtensionsPath)") >= 0 ||
project_attribute.IndexOf ("$(MSBuildExtensionsPath32)") >= 0 ||
project_attribute.IndexOf ("$(MSBuildExtensionsPath64)") >= 0;
bool condn_has_extn_ref = condition_attribute.IndexOf ("$(MSBuildExtensionsPath)") >= 0 ||
condition_attribute.IndexOf ("$(MSBuildExtensionsPath32)") >= 0 ||
condition_attribute.IndexOf ("$(MSBuildExtensionsPath64)") >= 0;
// we can skip the following logic in case the condition doesn't reference any extension paths
// and it evaluates to false since nothing would change anyway
if (!condn_has_extn_ref && !ConditionParser.ParseAndEvaluate (condition_attribute, project))
return;
string importingFile = importingProject != null ? importingProject.FullFileName : project.FullFileName;
DirectoryInfo base_dir_info = null;
if (!String.IsNullOrEmpty (importingFile))
base_dir_info = new DirectoryInfo (Path.GetDirectoryName (importingFile));
else
base_dir_info = new DirectoryInfo (Directory.GetCurrentDirectory ());
var importPaths = GetImportPathsFromString (project_attribute, project, base_dir_info);
var extensionPaths = GetExtensionPaths (project);
if (!has_extn_ref) {
foreach (var importPath in importPaths) {
foreach (var extensionPath in extensionPaths) {
has_extn_ref = has_extn_ref || importPath.IndexOf (extensionPath) >= 0;
}
}
}
IEnumerable<string> extn_paths = has_extn_ref ? extensionPaths : new string [] { null };
bool import_needed = false;
var currentLoadSettings = project.ProjectLoadSettings;
try {
foreach (var settings in new ProjectLoadSettings [] { ProjectLoadSettings.None, currentLoadSettings }) {
foreach (string path in extn_paths) {
string extn_msg = null;
if (has_extn_ref) {
project.SetExtensionsPathProperties (path);
extn_msg = "from extension path " + path;
}
// do this after setting new Extension properties, as condition might
// reference it
if (!ConditionParser.ParseAndEvaluate (condition_attribute, project))
continue;
import_needed = true;
project.ProjectLoadSettings = settings;
// We stop if atleast one file got imported.
// Remaining extension paths are *not* tried
bool atleast_one = false;
foreach (string importPath in importPaths) {
try {
if (func (importPath, extn_msg))
atleast_one = true;
} catch (Exception e) {
throw new InvalidProjectFileException (String.Format (
"{0}: Project file could not be imported, it was being imported by " +
"{1}: {2}", importPath, importingFile, e.Message), e);
}
}
if (atleast_one)
return;
}
}
} finally {
project.ProjectLoadSettings = currentLoadSettings;
if (has_extn_ref)
project.SetExtensionsPathProperties (Project.DefaultExtensionsPath);
}
if (import_needed)
throw new InvalidProjectFileException (String.Format ("{0} could not import \"{1}\"", importingFile, project_attribute));
}
// Parses the Project attribute from an Import,
// and returns the import filenames that match.
// This handles wildcards also
static IEnumerable<string> GetImportPathsFromString (string import_string, Project project, DirectoryInfo base_dir_info)
{
string parsed_import = Expression.ParseAs<string> (import_string, ParseOptions.AllowItemsNoMetadataAndSplit, project);
if (parsed_import != null)
parsed_import = parsed_import.Trim ();
if (String.IsNullOrEmpty (parsed_import))
throw new InvalidProjectFileException ("The required attribute \"Project\" in Import is empty");
if (DirectoryScanner.HasWildcard (parsed_import)) {
var directoryScanner = new DirectoryScanner () {
Includes = new ITaskItem [] { new TaskItem (parsed_import) },
BaseDirectory = base_dir_info
};
directoryScanner.Scan ();
foreach (ITaskItem matchedItem in directoryScanner.MatchedItems)
yield return matchedItem.ItemSpec;
} else
yield return parsed_import;
}
// Gives a list of extensions paths to try for $(MSBuildExtensionsPath),
// *in-order*
static IEnumerable<string> GetExtensionPaths (Project project)
{
// This is a *HACK* to support multiple paths for
// MSBuildExtensionsPath property. Normally it would
// get resolved to a single value, but here we special
// case it and try various paths, see the code below
//
// The property itself will resolve to the default
// location though, so you get that in any other part of the
// project.
string envvar = Environment.GetEnvironmentVariable ("MSBuildExtensionsPath");
envvar = String.Join (PathSeparatorAsString, new string [] {
(envvar ?? String.Empty),
// For mac osx, look in the 'External' dir on macosx,
// see bug #663180
MSBuildUtils.RunningOnMac ? MacOSXExternalXBuildDir : String.Empty,
DotConfigExtensionsPath,
Project.DefaultExtensionsPath});
var pathsTable = new Dictionary<string, string> ();
foreach (string extn_path in envvar.Split (new char [] {Path.PathSeparator}, StringSplitOptions.RemoveEmptyEntries)) {
if (pathsTable.ContainsKey (extn_path))
continue;
if (!Directory.Exists (extn_path)) {
if (extn_path != DotConfigExtensionsPath)
project.ParentEngine.LogMessage (
MessageImportance.Low,
"Extension path '{0}' not found, ignoring.",
extn_path);
continue;
}
pathsTable [extn_path] = extn_path;
yield return extn_path;
}
}
public string Condition {
get {
string s = importElement.GetAttribute ("Condition");
return s == String.Empty ? null : s;
}
}
public string EvaluatedProjectPath {
get { return evaluatedProjectPath; }
}
public bool IsImported {
get { return originalProject != null; }
}
public string ProjectPath {
get { return importElement.GetAttribute ("Project"); }
}
internal string ContainedInProjectFileName {
get { return originalProject != null ? originalProject.FullFileName : project.FullFileName; }
}
}
}