518 строки
18 KiB
C#
518 строки
18 KiB
C#
using System;
|
|
using System.Xml;
|
|
|
|
namespace xharness
|
|
{
|
|
static class ProjectFileExtensions {
|
|
const string MSBuild_Namespace = "http://schemas.microsoft.com/developer/msbuild/2003";
|
|
|
|
public static void SetProjectTypeGuids (this XmlDocument csproj, string value)
|
|
{
|
|
SetNode (csproj, "ProjectTypeGuids", value);
|
|
}
|
|
|
|
public static string GetProjectGuid (this XmlDocument csproj)
|
|
{
|
|
return csproj.SelectSingleNode ("/*/*/*[local-name() = 'ProjectGuid']").InnerText;
|
|
}
|
|
|
|
public static void SetProjectGuid (this XmlDocument csproj, string value)
|
|
{
|
|
csproj.SelectSingleNode ("/*/*/*[local-name() = 'ProjectGuid']").InnerText = value;
|
|
}
|
|
|
|
public static string GetOutputType (this XmlDocument csproj)
|
|
{
|
|
return csproj.SelectSingleNode ("/*/*/*[local-name() = 'OutputType']").InnerText;
|
|
}
|
|
|
|
public static void SetOutputType (this XmlDocument csproj, string value)
|
|
{
|
|
csproj.SelectSingleNode ("/*/*/*[local-name() = 'OutputType']").InnerText = value;
|
|
}
|
|
|
|
static string[] eqsplitter = new string[] { "==" };
|
|
static char[] pipesplitter = new char[] { '|' };
|
|
static char[] trimchars = new char[] { '\'', ' ' };
|
|
|
|
static void ParseConditions (this XmlNode node, out string platform, out string configuration)
|
|
{
|
|
// This parses the platform/configuration out of conditions like this:
|
|
//
|
|
// Condition=" '$(Configuration)|$(Platform)' == 'Debug|iPhoneSimulator' "
|
|
//
|
|
platform = "Any CPU";
|
|
configuration = "Debug";
|
|
while (node != null) {
|
|
if (node.Attributes != null) {
|
|
var conditionAttribute = node.Attributes ["Condition"];
|
|
if (conditionAttribute != null) {
|
|
var condition = conditionAttribute.Value;
|
|
var eqsplit = condition.Split (eqsplitter, StringSplitOptions.None);
|
|
if (eqsplit.Length == 2) {
|
|
var left = eqsplit [0].Trim (trimchars).Split (pipesplitter);
|
|
var right = eqsplit [1].Trim (trimchars).Split (pipesplitter);
|
|
if (left.Length == right.Length) {
|
|
for (int i = 0; i < left.Length; i++) {
|
|
switch (left [i]) {
|
|
case "$(Configuration)":
|
|
configuration = right [i];
|
|
break;
|
|
case "$(Platform)":
|
|
platform = right [i];
|
|
break;
|
|
default:
|
|
throw new Exception (string.Format ("Unknown condition logic: {0}", left [i]));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (string.IsNullOrEmpty (platform) || string.IsNullOrEmpty (configuration))
|
|
throw new Exception (string.Format ("Could not parse the condition: {0}", conditionAttribute.Value));
|
|
}
|
|
}
|
|
node = node.ParentNode;
|
|
}
|
|
|
|
if (string.IsNullOrEmpty (platform) || string.IsNullOrEmpty (configuration))
|
|
throw new Exception ("Could not find a condition attribute.");
|
|
}
|
|
|
|
public static void SetOutputPath (this XmlDocument csproj, string value)
|
|
{
|
|
var nodes = csproj.SelectNodes ("/*/*/*[local-name() = 'OutputPath']");
|
|
if (nodes.Count == 0)
|
|
throw new Exception ("Could not find node OutputPath");
|
|
foreach (XmlNode n in nodes) {
|
|
// OutputPath needs to be expanded, otherwise Xamarin Studio isn't able to launch the project.
|
|
string platform, configuration;
|
|
ParseConditions (n, out platform, out configuration);
|
|
n.InnerText = value.Replace ("$(Platform)", platform).Replace ("$(Configuration)", configuration);
|
|
}
|
|
}
|
|
|
|
static bool IsNodeApplicable (XmlNode node, string platform, string configuration)
|
|
{
|
|
while (node != null) {
|
|
if (!EvaluateCondition (node, platform, configuration))
|
|
return false;
|
|
node = node.ParentNode;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool EvaluateCondition (XmlNode node, string platform, string configuration)
|
|
{
|
|
if (node.Attributes == null)
|
|
return true;
|
|
|
|
var condition = node.Attributes ["Condition"];
|
|
if (condition == null)
|
|
return true;
|
|
|
|
var conditionValue = condition.Value;
|
|
conditionValue = conditionValue.Replace ("$(Configuration)", configuration).Replace ("$(Platform)", platform);
|
|
|
|
var eqsplit = conditionValue.Split (eqsplitter, StringSplitOptions.None);
|
|
if (eqsplit.Length != 2) {
|
|
Console.WriteLine ("Could not parse condition; {0}", conditionValue);
|
|
return false;
|
|
}
|
|
|
|
var left = eqsplit [0].Trim (trimchars);
|
|
var right = eqsplit [1].Trim (trimchars);
|
|
return left == right;
|
|
}
|
|
|
|
public static string GetOutputPath (this XmlDocument csproj, string platform, string configuration)
|
|
{
|
|
var nodes = csproj.SelectNodes ("/*/*/*[local-name() = 'OutputPath']");
|
|
if (nodes.Count == 0)
|
|
throw new Exception ("Could not find node OutputPath");
|
|
|
|
foreach (XmlNode n in nodes) {
|
|
if (IsNodeApplicable (n, platform, configuration))
|
|
return n.InnerText;
|
|
}
|
|
throw new Exception ("Could not find OutputPath");
|
|
}
|
|
|
|
public static void SetIntermediateOutputPath (this XmlDocument csproj, string value)
|
|
{
|
|
var project = csproj.ChildNodes [1];
|
|
var property_group = project.ChildNodes [0];
|
|
|
|
var intermediateOutputPath = csproj.CreateElement ("IntermediateOutputPath", MSBuild_Namespace);
|
|
intermediateOutputPath.InnerText = value;
|
|
property_group.AppendChild (intermediateOutputPath);
|
|
}
|
|
|
|
public static void SetTargetFrameworkIdentifier (this XmlDocument csproj, string value)
|
|
{
|
|
SetTopLevelPropertyGroupValue (csproj, "TargetFrameworkIdentifier", value);
|
|
}
|
|
|
|
public static void SetTopLevelPropertyGroupValue (this XmlDocument csproj, string key, string value)
|
|
{
|
|
var firstPropertyGroups = csproj.SelectNodes ("//*[local-name() = 'PropertyGroup']")[0];
|
|
var targetFrameworkIdentifierNode = firstPropertyGroups.SelectSingleNode (string.Format ("//*[local-name() = '{0}']", key));
|
|
if (targetFrameworkIdentifierNode != null)
|
|
{
|
|
SetNode (csproj, key, value);
|
|
}
|
|
else
|
|
{
|
|
var mea = csproj.CreateElement (key, MSBuild_Namespace);
|
|
mea.InnerText = value;
|
|
firstPropertyGroups.AppendChild (mea);
|
|
}
|
|
}
|
|
|
|
public static void RemoveTargetFrameworkIdentifier (this XmlDocument csproj)
|
|
{
|
|
RemoveNode (csproj, "TargetFrameworkIdentifier");
|
|
}
|
|
|
|
public static void SetAssemblyName (this XmlDocument csproj, string value)
|
|
{
|
|
SetNode (csproj, "AssemblyName", value);
|
|
}
|
|
|
|
public static string GetAssemblyName (this XmlDocument csproj)
|
|
{
|
|
return csproj.SelectSingleNode ("/*/*/*[local-name() = 'AssemblyName']").InnerText;
|
|
}
|
|
|
|
public static void SetPlatformAssembly (this XmlDocument csproj, string value)
|
|
{
|
|
SetAssemblyReference (csproj, "monotouch", value);
|
|
}
|
|
|
|
public static void SetAssemblyReference (this XmlDocument csproj, string current, string value)
|
|
{
|
|
var project = csproj.ChildNodes [1];
|
|
var reference = csproj.SelectSingleNode ("/*/*/*[local-name() = 'Reference' and @Include = '" + current + "']");
|
|
if (reference != null)
|
|
reference.Attributes ["Include"].Value = value;
|
|
}
|
|
|
|
public static void RemoveReferences (this XmlDocument csproj, string projectName)
|
|
{
|
|
var reference = csproj.SelectSingleNode ("/*/*/*[local-name() = 'Reference' and @Include = '" + projectName + "']");
|
|
if (reference != null)
|
|
reference.ParentNode.RemoveChild (reference);
|
|
}
|
|
|
|
public static void SetHintPath (this XmlDocument csproj, string current, string value)
|
|
{
|
|
var project = csproj.ChildNodes [1];
|
|
var reference = csproj.SelectSingleNode ("/*/*/*[local-name() = 'Reference' and @Include = '" + current + "']");
|
|
if (reference != null) {
|
|
var hintPath = csproj.CreateElement ("HintPath", MSBuild_Namespace);
|
|
hintPath.InnerText = value;
|
|
reference.AppendChild (hintPath);
|
|
}
|
|
}
|
|
|
|
public static void AddCompileInclude (this XmlDocument csproj, string link, string include)
|
|
{
|
|
var compile_node = csproj.SelectSingleNode ("//*[local-name() = 'Compile']");
|
|
var item_group = compile_node.ParentNode;
|
|
|
|
var node = csproj.CreateElement ("Compile", MSBuild_Namespace);
|
|
var include_attribute = csproj.CreateAttribute ("Include");
|
|
include_attribute.Value = include;
|
|
node.Attributes.Append (include_attribute);
|
|
var linkElement = csproj.CreateElement ("Link", MSBuild_Namespace);
|
|
linkElement.InnerText = link;
|
|
node.AppendChild (linkElement);
|
|
item_group.AppendChild (node);
|
|
}
|
|
|
|
public static void SetImport (this XmlDocument csproj, string value)
|
|
{
|
|
var imports = csproj.SelectNodes ("/*/*[local-name() = 'Import']");
|
|
if (imports.Count != 1)
|
|
throw new Exception ("More than one import");
|
|
imports [0].Attributes ["Project"].Value = value;
|
|
}
|
|
|
|
public static void SetExtraLinkerDefs (this XmlDocument csproj, string value)
|
|
{
|
|
var mtouchExtraArgs = csproj.SelectNodes ("//*[local-name() = 'MtouchExtraArgs']");
|
|
foreach (XmlNode mea in mtouchExtraArgs)
|
|
mea.InnerText = mea.InnerText.Replace ("extra-linker-defs.xml", value);
|
|
var nones = csproj.SelectNodes ("//*[local-name() = 'None' and @Include = 'extra-linker-defs.xml']");
|
|
foreach (XmlNode none in nones)
|
|
none.Attributes ["Include"].Value = value;
|
|
}
|
|
|
|
public static void AddExtraMtouchArgs (this XmlDocument csproj, string value, string platform, string configuration)
|
|
{
|
|
var mtouchExtraArgs = csproj.SelectNodes ("//*[local-name() = 'MtouchExtraArgs']");
|
|
var found = false;
|
|
foreach (XmlNode mea in mtouchExtraArgs) {
|
|
if (!IsNodeApplicable (mea, platform, configuration))
|
|
continue;
|
|
|
|
mea.InnerText += " " + value;
|
|
found = true;
|
|
}
|
|
|
|
if (found)
|
|
return;
|
|
|
|
// Not all projects have a MtouchExtraArgs node, so create one of none was found.
|
|
var propertyGroups = csproj.SelectNodes ("//*[local-name() = 'PropertyGroup' and @Condition]");
|
|
foreach (XmlNode pg in propertyGroups) {
|
|
if (!EvaluateCondition (pg, platform, configuration))
|
|
continue;
|
|
|
|
var mea = csproj.CreateElement ("MtouchExtraArgs", MSBuild_Namespace);
|
|
mea.InnerText = value;
|
|
pg.AppendChild (mea);
|
|
}
|
|
}
|
|
|
|
public static void SetMtouchUseLlvm (this XmlDocument csproj, bool value, string platform, string configuration)
|
|
{
|
|
SetNode (csproj, "MtouchUseLlvm", true ? "true" : "false", platform, configuration);
|
|
}
|
|
|
|
public static void SetMtouchUseBitcode (this XmlDocument csproj, bool value, string platform, string configuration)
|
|
{
|
|
SetNode (csproj, "MtouchEnableBitcode", true ? "true" : "false", platform, configuration);
|
|
}
|
|
|
|
public static void SetNode (this XmlDocument csproj, string node, string value, string platform, string configuration)
|
|
{
|
|
var projnode = csproj.SelectNodes ("//*[local-name() = '" + node + "']");
|
|
var found = false;
|
|
foreach (XmlNode xmlnode in projnode) {
|
|
if (!IsNodeApplicable (xmlnode, platform, configuration))
|
|
continue;
|
|
|
|
xmlnode.InnerText = value;
|
|
found = true;
|
|
}
|
|
|
|
if (found)
|
|
return;
|
|
|
|
// Not all projects have a MtouchExtraArgs node, so create one of none was found.
|
|
var propertyGroups = csproj.SelectNodes ("//*[local-name() = 'PropertyGroup' and @Condition]");
|
|
foreach (XmlNode pg in propertyGroups) {
|
|
if (!EvaluateCondition (pg, platform, configuration))
|
|
continue;
|
|
|
|
var mea = csproj.CreateElement (node, MSBuild_Namespace);
|
|
mea.InnerText = value;
|
|
pg.AppendChild (mea);
|
|
}
|
|
}
|
|
|
|
|
|
public static string GetImport (this XmlDocument csproj)
|
|
{
|
|
var imports = csproj.SelectNodes ("/*/*[local-name() = 'Import']");
|
|
if (imports.Count != 1)
|
|
throw new Exception ("More than one import");
|
|
return imports [0].Attributes ["Project"].Value;
|
|
}
|
|
|
|
public static void FixProjectReferences (this XmlDocument csproj, string suffix, Func<string, bool> fixCallback = null)
|
|
{
|
|
var nodes = csproj.SelectNodes ("/*/*/*[local-name() = 'ProjectReference']");
|
|
if (nodes.Count == 0)
|
|
return;
|
|
foreach (XmlNode n in nodes) {
|
|
var name = n ["Name"].InnerText;
|
|
if (fixCallback != null && !fixCallback (name))
|
|
continue;
|
|
var include = n.Attributes ["Include"];
|
|
include.Value = include.Value.Replace (".csproj", suffix + ".csproj");
|
|
include.Value = include.Value.Replace (".fsproj", suffix + ".fsproj");
|
|
}
|
|
}
|
|
|
|
public static void FixTestLibrariesReferences (this XmlDocument csproj, string platform)
|
|
{
|
|
var nodes = csproj.SelectNodes ("//*[local-name() = 'ObjcBindingNativeLibrary']");
|
|
foreach (XmlNode node in nodes) {
|
|
var includeAttribute = node.Attributes ["Include"];
|
|
if (includeAttribute != null)
|
|
includeAttribute.Value = includeAttribute.Value.Replace ("test-libraries\\.libs\\ios\\libtest.a", "test-libraries\\.libs\\" + platform + "\\libtest.a");
|
|
}
|
|
nodes = csproj.SelectNodes ("//*[local-name() = 'Target' and @Name = 'BeforeBuild']");
|
|
foreach (XmlNode node in nodes) {
|
|
var outputsAttribute = node.Attributes ["Outputs"];
|
|
if (outputsAttribute != null)
|
|
outputsAttribute.Value = outputsAttribute.Value.Replace ("test-libraries\\.libs\\ios\\libtest.a", "test-libraries\\.libs\\" + platform + "\\libtest.a");
|
|
}
|
|
}
|
|
|
|
public static void FixArchitectures (this XmlDocument csproj, string simulator_arch, string device_arch)
|
|
{
|
|
var nodes = csproj.SelectNodes ("/*/*/*[local-name() = 'MtouchArch']");
|
|
if (nodes.Count == 0)
|
|
throw new Exception (string.Format ("Could not find MtouchArch at all"));
|
|
foreach (XmlNode n in nodes) {
|
|
switch (n.InnerText.ToLower ()) {
|
|
case "i386":
|
|
case "x86_64":
|
|
n.InnerText = simulator_arch;
|
|
break;
|
|
case "armv7":
|
|
case "armv7s":
|
|
case "arm64":
|
|
n.InnerText = device_arch;
|
|
break;
|
|
default:
|
|
throw new NotImplementedException (string.Format ("Unhandled architecture: {0}", n.Value));
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void FindAndReplace (this XmlDocument csproj, string find, string replace)
|
|
{
|
|
FindAndReplace (csproj.ChildNodes, find, replace);
|
|
}
|
|
|
|
static void FindAndReplace (XmlNode node, string find, string replace)
|
|
{
|
|
if (node.HasChildNodes) {
|
|
FindAndReplace (node.ChildNodes, find, replace);
|
|
} else {
|
|
if (node.NodeType == XmlNodeType.Text)
|
|
node.InnerText = node.InnerText.Replace (find, replace);
|
|
}
|
|
if (node.Attributes != null) {
|
|
foreach (XmlAttribute attrib in node.Attributes)
|
|
attrib.Value = attrib.Value.Replace (find, replace);
|
|
}
|
|
}
|
|
|
|
static void FindAndReplace (XmlNodeList nodes, string find, string replace)
|
|
{
|
|
foreach (XmlNode node in nodes)
|
|
FindAndReplace (node, find, replace);
|
|
}
|
|
|
|
public static void FixInfoPListInclude (this XmlDocument csproj, string suffix)
|
|
{
|
|
var import = csproj.SelectSingleNode ("/*/*/*[local-name() = 'None' and @Include = 'Info.plist']");
|
|
import.Attributes ["Include"].Value = "Info" + suffix + ".plist";
|
|
var logicalName = csproj.CreateElement ("LogicalName", MSBuild_Namespace);
|
|
logicalName.InnerText = "Info.plist";
|
|
import.AppendChild (logicalName);
|
|
}
|
|
|
|
public static string GetInfoPListInclude (this XmlDocument csproj)
|
|
{
|
|
var logicalNames = csproj.SelectNodes ("//*[local-name() = 'LogicalName']");
|
|
foreach (XmlNode ln in logicalNames) {
|
|
if (ln.InnerText != "Info.plist")
|
|
continue;
|
|
return ln.ParentNode.Attributes ["Include"].Value;
|
|
}
|
|
var nones = csproj.SelectNodes ("//*[local-name() = 'None' and @Include = 'Info.plist']");
|
|
if (nones.Count > 0)
|
|
return "Info.plist";
|
|
throw new Exception ("Could not find Info.plist include");
|
|
}
|
|
|
|
public static void SetProjectReferenceValue (this XmlDocument csproj, string projectInclude, string node, string value)
|
|
{
|
|
var nameNode = csproj.SelectSingleNode ("//*[local-name() = 'ProjectReference' and @Include = '" + projectInclude + "']/*[local-name() = '" + node + "']");
|
|
nameNode.InnerText = value;
|
|
}
|
|
|
|
public static void CreateProjectReferenceValue (this XmlDocument csproj, string existingInclude, string path, string guid, string name)
|
|
{
|
|
var referenceNode = csproj.SelectSingleNode ("//*[local-name() = 'Reference' and @Include = '" + existingInclude + "']");
|
|
var projectReferenceNode = csproj.CreateElement ("ProjectReference", MSBuild_Namespace);
|
|
var includeAttribute = csproj.CreateAttribute ("Include");
|
|
includeAttribute.Value = path.Replace ('/', '\\');
|
|
projectReferenceNode.Attributes.Append (includeAttribute);
|
|
var projectNode = csproj.CreateElement ("Project", MSBuild_Namespace);
|
|
projectNode.InnerText = guid;
|
|
projectReferenceNode.AppendChild (projectNode);
|
|
var nameNode = csproj.CreateElement ("Name", MSBuild_Namespace);
|
|
nameNode.InnerText = name;
|
|
projectReferenceNode.AppendChild (nameNode);
|
|
|
|
XmlNode itemGroup;
|
|
if (referenceNode != null) {
|
|
itemGroup = referenceNode.ParentNode;
|
|
referenceNode.ParentNode.RemoveChild (referenceNode);
|
|
} else {
|
|
itemGroup = csproj.CreateElement ("ItemGroup", MSBuild_Namespace);
|
|
csproj.SelectSingleNode ("//*[local-name() = 'Project']").AppendChild (itemGroup);
|
|
}
|
|
itemGroup.AppendChild (projectReferenceNode);
|
|
}
|
|
|
|
public static void AddAdditionalDefines (this XmlDocument csproj, string value)
|
|
{
|
|
var mainPropertyGroup = csproj.SelectSingleNode ("//*[local-name() = 'PropertyGroup' and not(@Condition)]");
|
|
var mainDefine = mainPropertyGroup.SelectSingleNode ("/*[local-name() = 'DefineConstants']");
|
|
if (mainDefine == null) {
|
|
mainDefine = csproj.CreateElement ("DefineConstants", MSBuild_Namespace);
|
|
mainDefine.InnerText = value;
|
|
mainPropertyGroup.AppendChild (mainDefine);
|
|
} else {
|
|
mainDefine.InnerText = mainDefine.InnerText + ";" + value;
|
|
}
|
|
|
|
// make sure all other DefineConstants include the main one
|
|
var otherDefines = csproj.SelectNodes ("//*[local-name() = 'PropertyGroup' and @Condition]/*[local-name() = 'DefineConstants']");
|
|
foreach (XmlNode def in otherDefines) {
|
|
if (!def.InnerText.Contains ("$(DefineConstants"))
|
|
def.InnerText = def.InnerText + ";$(DefineConstants)";
|
|
}
|
|
}
|
|
|
|
public static void SetNode (this XmlDocument csproj, string node, string value)
|
|
{
|
|
var nodes = csproj.SelectNodes ("/*/*/*[local-name() = '" + node + "']");
|
|
if (nodes.Count == 0)
|
|
throw new Exception (string.Format ("Could not find node {0}", node));
|
|
foreach (XmlNode n in nodes) {
|
|
n.InnerText = value;
|
|
}
|
|
}
|
|
|
|
public static void RemoveNode (this XmlDocument csproj, string node)
|
|
{
|
|
var nodes = csproj.SelectNodes ("/*/*/*[local-name() = '" + node + "']");
|
|
if (nodes.Count == 0)
|
|
throw new Exception (string.Format ("Could not find node {0}", node));
|
|
foreach (XmlNode n in nodes) {
|
|
n.ParentNode.RemoveChild (n);
|
|
}
|
|
}
|
|
|
|
public static void CloneConfiguration (this XmlDocument csproj, string platform, string configuration, string new_configuration)
|
|
{
|
|
var projnode = csproj.SelectNodes ("//*[local-name() = 'PropertyGroup']");
|
|
foreach (XmlNode xmlnode in projnode) {
|
|
if (xmlnode.Attributes ["Condition"] == null)
|
|
continue;
|
|
if (!IsNodeApplicable (xmlnode, platform, configuration))
|
|
continue;
|
|
|
|
var clone = xmlnode.Clone ();
|
|
var condition = clone.Attributes ["Condition"];
|
|
condition.InnerText = condition.InnerText.Replace (configuration, new_configuration);
|
|
xmlnode.ParentNode.InsertAfter (clone, xmlnode);
|
|
return;
|
|
}
|
|
|
|
throw new Exception ("Configuration not found.");
|
|
}
|
|
}
|
|
}
|
|
|