// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
namespace WixToolset.BuildTasks
{
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
///
/// An MSBuild task to create a list of preprocessor defines to be passed to candle from the
/// list of referenced projects.
///
public sealed class CreateProjectReferenceDefineConstants : Task
{
private ITaskItem[] defineConstants;
private ITaskItem[] projectConfigurations;
private ITaskItem[] projectReferencePaths;
[Output]
public ITaskItem[] DefineConstants
{
get { return this.defineConstants; }
}
[Required]
public ITaskItem[] ProjectReferencePaths
{
get { return this.projectReferencePaths; }
set { this.projectReferencePaths = value; }
}
public ITaskItem[] ProjectConfigurations
{
get { return this.projectConfigurations; }
set { this.projectConfigurations = value; }
}
public override bool Execute()
{
List outputItems = new List();
Dictionary defineConstants = new Dictionary();
for (int i = 0; i < this.ProjectReferencePaths.Length; i++)
{
ITaskItem item = this.ProjectReferencePaths[i];
string configuration = item.GetMetadata("Configuration");
string fullConfiguration = item.GetMetadata("FullConfiguration");
string platform = item.GetMetadata("Platform");
string projectPath = CreateProjectReferenceDefineConstants.GetProjectPath(this.ProjectReferencePaths, i);
string projectDir = Path.GetDirectoryName(projectPath) + Path.DirectorySeparatorChar;
string projectExt = Path.GetExtension(projectPath);
string projectFileName = Path.GetFileName(projectPath);
string projectName = Path.GetFileNameWithoutExtension(projectPath);
string referenceName = CreateProjectReferenceDefineConstants.GetReferenceName(item, projectName);
string targetPath = item.GetMetadata("FullPath");
string targetDir = Path.GetDirectoryName(targetPath) + Path.DirectorySeparatorChar;
string targetExt = Path.GetExtension(targetPath);
string targetFileName = Path.GetFileName(targetPath);
string targetName = Path.GetFileNameWithoutExtension(targetPath);
// If there is no configuration metadata on the project reference task item,
// check for any additional configuration data provided in the optional task property.
if (String.IsNullOrEmpty(fullConfiguration))
{
fullConfiguration = this.FindProjectConfiguration(projectName);
if (!String.IsNullOrEmpty(fullConfiguration))
{
string[] typeAndPlatform = fullConfiguration.Split('|');
configuration = typeAndPlatform[0];
platform = (typeAndPlatform.Length > 1 ? typeAndPlatform[1] : String.Empty);
}
}
// write out the platform/configuration defines
defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.Configuration", referenceName)] = configuration;
defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.FullConfiguration", referenceName)] = fullConfiguration;
defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.Platform", referenceName)] = platform;
// write out the ProjectX defines
defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.ProjectDir", referenceName)] = projectDir;
defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.ProjectExt", referenceName)] = projectExt;
defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.ProjectFileName", referenceName)] = projectFileName;
defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.ProjectName", referenceName)] = projectName;
defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.ProjectPath", referenceName)] = projectPath;
// write out the TargetX defines
string targetDirDefine = String.Format(CultureInfo.InvariantCulture, "{0}.TargetDir", referenceName);
if (defineConstants.ContainsKey(targetDirDefine))
{
//if target dir was already defined, redefine it as the common root shared by multiple references from the same project
string commonDir = FindCommonRoot(targetDir, defineConstants[targetDirDefine]);
if (!String.IsNullOrEmpty(commonDir))
{
targetDir = commonDir;
}
}
defineConstants[targetDirDefine] = CreateProjectReferenceDefineConstants.EnsureEndsWithBackslash(targetDir);
defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.TargetExt", referenceName)] = targetExt;
defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.TargetFileName", referenceName)] = targetFileName;
defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.TargetName", referenceName)] = targetName;
//if target path was already defined, append to it creating a list of multiple references from the same project
string targetPathDefine = String.Format(CultureInfo.InvariantCulture, "{0}.TargetPath", referenceName);
if (defineConstants.ContainsKey(targetPathDefine))
{
string oldTargetPath = defineConstants[targetPathDefine];
if (!targetPath.Equals(oldTargetPath, StringComparison.OrdinalIgnoreCase))
{
defineConstants[targetPathDefine] += ";" + targetPath;
}
//If there was only one targetpath we need to create its culture specific define
if (!oldTargetPath.Contains(";"))
{
string oldSubFolder = FindSubfolder(oldTargetPath, targetDir, targetFileName);
if (!String.IsNullOrEmpty(oldSubFolder))
{
defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.{1}.TargetPath", referenceName, oldSubFolder.Replace('\\', '_'))] = oldTargetPath;
}
}
// Create a culture specific define
string subFolder = FindSubfolder(targetPath, targetDir, targetFileName);
if (!String.IsNullOrEmpty(subFolder))
{
defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.{1}.TargetPath", referenceName, subFolder.Replace('\\', '_'))] = targetPath;
}
}
else
{
defineConstants[targetPathDefine] = targetPath;
}
}
foreach (KeyValuePair define in defineConstants)
{
outputItems.Add(new TaskItem(String.Format(CultureInfo.InvariantCulture, "{0}={1}", define.Key, define.Value)));
}
this.defineConstants = outputItems.ToArray();
return true;
}
public static string GetProjectPath(ITaskItem[] projectReferencePaths, int i)
{
return projectReferencePaths[i].GetMetadata("MSBuildSourceProjectFile");
}
public static string GetReferenceName(ITaskItem item, string projectName)
{
string referenceName = item.GetMetadata("Name");
if (String.IsNullOrEmpty(referenceName))
{
referenceName = projectName;
}
// We cannot have an equals sign in the variable name because it
// messes with the preprocessor definitions on the command line.
referenceName = referenceName.Replace('=', '_');
// We cannot have a double quote on the command line because it
// there is no way to escape it on the command line.
referenceName = referenceName.Replace('\"', '_');
// We cannot have parens in the variable name because the WiX
// preprocessor will not be able to parse it.
referenceName = referenceName.Replace('(', '_');
referenceName = referenceName.Replace(')', '_');
return referenceName;
}
///
/// Look through the configuration data in the ProjectConfigurations property
/// to find the configuration for a project, if available.
///
/// Name of the project that is being searched for.
/// Full configuration spec, for example "Release|Win32".
private string FindProjectConfiguration(string projectName)
{
string configuration = String.Empty;
if (this.ProjectConfigurations != null)
{
foreach (ITaskItem configItem in this.ProjectConfigurations)
{
string configProject = configItem.ItemSpec;
if (configProject.Length > projectName.Length &&
configProject.StartsWith(projectName) &&
configProject[projectName.Length] == '=')
{
configuration = configProject.Substring(projectName.Length + 1);
break;
}
}
}
return configuration;
}
///
/// Finds the common root between two paths
///
///
///
/// common root on success, empty string on failure
private static string FindCommonRoot(string path1, string path2)
{
path1 = path1.TrimEnd(Path.DirectorySeparatorChar);
path2 = path2.TrimEnd(Path.DirectorySeparatorChar);
while (!String.IsNullOrEmpty(path1))
{
for (string searchPath = path2; !String.IsNullOrEmpty(searchPath); searchPath = Path.GetDirectoryName(searchPath))
{
if (path1.Equals(searchPath, StringComparison.OrdinalIgnoreCase))
{
return searchPath;
}
}
path1 = Path.GetDirectoryName(path1);
}
return path1;
}
///
/// Finds the subfolder of a path, excluding a root and filename.
///
/// Path to examine
/// Root that must be present
///
///
private static string FindSubfolder(string path, string rootPath, string fileName)
{
if (Path.GetFileName(path).Equals(fileName, StringComparison.OrdinalIgnoreCase))
{
path = Path.GetDirectoryName(path);
}
if (path.StartsWith(rootPath, StringComparison.OrdinalIgnoreCase))
{
// cut out the root and return the subpath
return path.Substring(rootPath.Length).Trim(Path.DirectorySeparatorChar);
}
return String.Empty;
}
private static string EnsureEndsWithBackslash(string dir)
{
if (dir[dir.Length - 1] != Path.DirectorySeparatorChar)
{
dir += Path.DirectorySeparatorChar;
}
return dir;
}
}
}