csproj项目工程文件的脚本/动态链接库设置

csproj项目工程文件的脚本/动态链接库设置

最近整理到很久之前项目中的一段偷懒处理,主要针对新加入U3D项目的插件、脚本、动态链接库等没有正常设置,导致在使用VS打开时报错,也没办法使用其自动补齐、智能提示等偷懒功能,甚至连相关定义都看不到,全局搜索时甚至搜索不到的情况。

主要原理是根据文件所在路径判断其编译关系,是放在4种csproj工程文件的具体哪一个。其他废话不多说了,直接上代码:

using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Xml;

public class CSProjTools 
{
	[MenuItem("Assets/Add2CSProj")]
	static void Add2CSProj()
	{
		//判断路径是否包含Editor、Plugins、Standard Assets
		//根据包含情况,加载读取项目目录下的.csproj文件
		if(Selection.activeObject == null)
		{
			return;
		}
		string tProjPath = Application.dataPath.Replace("Assets", "");
		var tArr = tProjPath.Split('/');
		string tProjName = tArr[tArr.Length - 2];
		string tSelectPath = tProjPath + AssetDatabase.GetAssetPath(Selection.activeObject);
		if(Directory.Exists(tSelectPath))	//选中文件夹,须递归遍历文件
		{
			var tFileArr = Directory.GetFiles(tSelectPath, "*.*", SearchOption.AllDirectories).Where(s => s.EndsWith(".cs") || s.EndsWith(".dll"));
			foreach(var filepath in tFileArr)
			{
				string tCSProjFilePath = tProjPath + tProjName + getCSProjFileName(filepath);
				insertCSProjFile(tCSProjFilePath, filepath);
			}
		}
		else //if(File.Exists(tSelectPath))	//选中的是文件
		{
			tSelectPath = tSelectPath.Replace(tProjPath, "");
			string tCSProjFilePath = tProjPath + tProjName + getCSProjFileName(tSelectPath);
			insertCSProjFile(tCSProjFilePath, tSelectPath);
		}
	}
	static string getCSProjFileName(string _filePath)
	{
		_filePath = _filePath.Replace("\", "/");
		if(_filePath.Contains("/Editor/"))
		{
			if(_filePath.Contains("Assets/Plugins/") || _filePath.Contains("Assets/Standard Assets/"))
			{
				return ".CSharp.Editor.Plugins.csproj";
			}
			else
			{
				return ".CSharp.Editor.csproj";
			}
		}
		else
		{
			if(_filePath.Contains("Assets/Plugins/") || _filePath.Contains("Assets/Standard Assets/"))
			{
				return ".CSharp.Plugins.csproj";
			}
			else
			{
				return ".CSharp.csproj";
			}
		}
	}
	static void insertCSProjFile(string _csprojFilePath, string _filePath)
	{
		XmlDocument tXmlDoc = new XmlDocument();
		tXmlDoc.Load(_csprojFilePath);

		string tNameSpace = "http://schemas.microsoft.com/developer/msbuild/2003";
		XmlNamespaceManager tNameSpaceMgr = new XmlNamespaceManager(tXmlDoc.NameTable);
		tNameSpaceMgr.AddNamespace("ns", tNameSpace);	//不加入namespace会找不到
		XmlNode tRootNode = tXmlDoc.SelectSingleNode("ns:Project", tNameSpaceMgr);
		XmlNodeList tGroupLst = tRootNode.SelectNodes("ns:ItemGroup", tNameSpaceMgr);
		//XmlNodeList tGroupLst = tXmlDoc.SelectNodes("/ns:Project/ns:ItemGroup", tNameSpaceMgr);

		foreach(XmlNode item in tGroupLst)
		{
			XmlElement tGroupElem = (XmlElement)item;

			//暂不考虑不包含子节点的情况
			if(_filePath.EndsWith(".dll") && item.FirstChild.Name == "Reference")		//dll动态链接库
			{
				var tArr = _filePath.Split('/');
				string tValue = tArr[tArr.Length - 1].Replace(".dll", "");
				foreach(XmlNode child in item.ChildNodes)
				{
					foreach(XmlAttribute attr in child.Attributes)
					{
						if(attr.Name == "Include" && attr.Value == tValue)
						{
							return;
						}
					}
				}
				XmlElement tChild = (XmlElement)item.FirstChild.CloneNode(true);
				tChild.SetAttribute("Include", tValue);

				XmlElement tSubChild = tXmlDoc.CreateElement("HintPath", tNameSpace);
				tSubChild.InnerText = _filePath.Replace("/", "\");

				tChild.AppendChild(tSubChild);
				item.AppendChild(tChild);
				break;
			}
			else if((_filePath.EndsWith(".cs") && item.FirstChild.Name == "Compile") || item.FirstChild.Name == "None")	//CS脚本或资源
			{
				foreach(XmlNode child in item.ChildNodes)
				{
					foreach(XmlAttribute attr in child.Attributes)
					{
						if(attr.Name == "Include" && attr.Value == _filePath)
						{
							return;
						}
					}
				}
				XmlElement tChild = (XmlElement)item.FirstChild.CloneNode(true);
				//XmlElement tChild = tXmlDoc.CreateElement(item.FirstChild.Name, tNameSpace);
				tChild.SetAttribute("Include", _filePath.Replace("/", "\"));
				item.AppendChild(tChild);
				break;
			}
		}
		tXmlDoc.Save(_csprojFilePath);
	}
}

  美中不足之处是每次都需要重新加载工程,不如使用右键的方式逐个加入文件和引用来的方便。后来也有考虑使用VSLangProj.dll结合EnvDTE一起使用来进一步完善,不过当时够用就罢手了。再后来也就一直没有时间重新拿起。

//todo:考虑将同一dll加入到多个csproj的情况
	[MenuItem("Assets/Add2ExtraCSProj/CS")]
	static void Add2CSProjCS()
	{
		if(Selection.activeObject == null)
		{
			return;
		}
		string tProjPath = Application.dataPath.Replace("Assets", "");
		var tArr = tProjPath.Split('/');
		string tProjName = tArr[tArr.Length - 2];
		string tSelectPath = tProjPath + AssetDatabase.GetAssetPath(Selection.activeObject);	//暂不考虑选中多文件的情况
		tSelectPath = tSelectPath.Replace(tProjPath, "");
		string tCSProjFilePath = tProjPath + tProjName + ".CSharp.csproj";
		insertCSProjFile(tCSProjFilePath, tSelectPath);
	}
	[MenuItem("Assets/Add2ExtraCSProj/CSPlugin")]
	static void Add2CSProjCSPlugin()
	{
		if(Selection.activeObject == null)
		{
			return;
		}
		string tProjPath = Application.dataPath.Replace("Assets", "");
		var tArr = tProjPath.Split('/');
		string tProjName = tArr[tArr.Length - 2];
		string tSelectPath = tProjPath + AssetDatabase.GetAssetPath(Selection.activeObject);	//暂不考虑选中多文件的情况
		tSelectPath = tSelectPath.Replace(tProjPath, "");
		string tCSProjFilePath = tProjPath + tProjName + ".CSharp.Plugins.csproj";
		insertCSProjFile(tCSProjFilePath, tSelectPath);
	}
	[MenuItem("Assets/Add2ExtraCSProj/CSEditor")]
	static void Add2CSProjCSEditor()
	{
		if(Selection.activeObject == null)
		{
			return;
		}
		string tProjPath = Application.dataPath.Replace("Assets", "");
		var tArr = tProjPath.Split('/');
		string tProjName = tArr[tArr.Length - 2];
		string tSelectPath = tProjPath + AssetDatabase.GetAssetPath(Selection.activeObject);	//暂不考虑选中多文件的情况
		tSelectPath = tSelectPath.Replace(tProjPath, "");
		string tCSProjFilePath = tProjPath + tProjName + ".CSharp.Editor.csproj";
		insertCSProjFile(tCSProjFilePath, tSelectPath);
	}
	[MenuItem("Assets/Add2ExtraCSProj/CSEditorPlugin")]
	static void Add2CSProjCSEditorPlugin()
	{
		if(Selection.activeObject == null)
		{
			return;
		}
		string tProjPath = Application.dataPath.Replace("Assets", "");
		var tArr = tProjPath.Split('/');
		string tProjName = tArr[tArr.Length - 2];
		string tSelectPath = tProjPath + AssetDatabase.GetAssetPath(Selection.activeObject);	//暂不考虑选中多文件的情况
		tSelectPath = tSelectPath.Replace(tProjPath, "");
		string tCSProjFilePath = tProjPath + tProjName + ".CSharp.Editor.Plugins.csproj";
		insertCSProjFile(tCSProjFilePath, tSelectPath);
	}



	[MenuItem("Assets/Add2CSProj")]
	static void Add2CSProj()
	{
		//判断路径是否包含Editor、Plugins、Standard Assets等
		//根据包含情况,加载读取项目目录下的.csproj文件
		if(Selection.activeObject == null)
		{
			return;
		}
		string tProjPath = Application.dataPath.Replace("Assets", "");
		var tArr = tProjPath.Split('/');
		string tProjName = tArr[tArr.Length - 2];
		string tSelectPath = tProjPath + AssetDatabase.GetAssetPath(Selection.activeObject);	//暂不考虑选中多文件的情况
		if(Directory.Exists(tSelectPath))	//选中文件夹,须递归遍历文件
		{
			var tFileArr = Directory.GetFiles(tSelectPath, "*.*", SearchOption.AllDirectories).Where(s => s.EndsWith(".cs") || s.EndsWith(".dll"));
			foreach(var filepath in tFileArr)
			{
				string tCSProjFilePath = tProjPath + tProjName + getCSProjFileName(filepath);
				insertCSProjFile(tCSProjFilePath, filepath, false);
			}
			//统一存储,防止IO效率及冲突
			foreach(var kv in xmlDocCacheMap)
			{
				if(insertCountMap.ContainsKey(kv.Key) && insertCountMap[kv.Key] > 0)
				{
					kv.Value.Save(kv.Key);
				}
			}
			xmlDocCacheMap.Clear();
			xmlNodeLstCacheMap.Clear();
		}
		else //if(File.Exists(tSelectPath))	//选中的是文件
		{
			tSelectPath = tSelectPath.Replace(tProjPath, "");
			string tCSProjFilePath = tProjPath + tProjName + getCSProjFileName(tSelectPath);
			insertCSProjFile(tCSProjFilePath, tSelectPath);
		}
		string tTipStr = "Add2CSProj Finished: 
";
		foreach(var kv in insertCountMap)
		{
			tTipStr += "	" + kv.Key + ": " + kv.Value;
		}
		insertCountMap.Clear();
		Debug.LogError(tTipStr);
	}
	static string getCSProjFileName(string _filePath)
	{
		_filePath = _filePath.Replace("\", "/");
		if(_filePath.Contains("/Editor/"))
		{
			if(_filePath.Contains("Assets/Plugins/") || _filePath.Contains("Assets/Standard Assets/"))
			{
				return ".CSharp.Editor.Plugins.csproj";
			}
			else
			{
				return ".CSharp.Editor.csproj";
			}
		}
		else
		{
			if(_filePath.Contains("Assets/Plugins/") || _filePath.Contains("Assets/Standard Assets/"))
			{
				return ".CSharp.Plugins.csproj";
			}
			else
			{
				return ".CSharp.csproj";
			}
		}
	}

	static string xmlNameSpace = "http://schemas.microsoft.com/developer/msbuild/2003";
	static IDictionary<string, XmlDocument> xmlDocCacheMap = new Dictionary<string, XmlDocument>();
	static IDictionary<string, XmlNodeList> xmlNodeLstCacheMap = new Dictionary<string, XmlNodeList>();
	static IDictionary<string, int> insertCountMap = new Dictionary<string, int>();
	static void insertCSProjFile(string _csprojFilePath, string _filePath, bool _syncSave = true)
	{
		if(_filePath.StartsWith(Application.dataPath))
		{
			_filePath = _filePath.Replace(Application.dataPath, "Assets");
		}
		//Debug.LogError(_csprojFilePath + "	" + _filePath);

		XmlDocument tXmlDoc = null;
		XmlNodeList tGroupLst = null;
		if(xmlDocCacheMap.ContainsKey(_csprojFilePath))
		{
			tXmlDoc = xmlDocCacheMap[_csprojFilePath];
			tGroupLst = xmlNodeLstCacheMap[_csprojFilePath];
		}
		else
		{
			tXmlDoc = new XmlDocument();
			tXmlDoc.Load(_csprojFilePath);
			
			XmlNamespaceManager tNameSpaceMgr = new XmlNamespaceManager(tXmlDoc.NameTable);
			tNameSpaceMgr.AddNamespace("ns", xmlNameSpace);	//不加入namespace会找不到
			XmlNode tRootNode = tXmlDoc.SelectSingleNode("ns:Project", tNameSpaceMgr);
			tGroupLst = tRootNode.SelectNodes("ns:ItemGroup", tNameSpaceMgr);
			//tGroupLst = tXmlDoc.SelectNodes("/ns:Project/ns:ItemGroup", tNameSpaceMgr);

			xmlDocCacheMap[_csprojFilePath] = tXmlDoc;
			xmlNodeLstCacheMap[_csprojFilePath] = tGroupLst;
		}

		//Debug.LogError(_filePath);
		_filePath = _filePath.Replace("/", "\");
		foreach(XmlNode item in tGroupLst)
		{
			XmlElement tGroupElem = (XmlElement)item;
			//Debug.LogError(item.FirstChild.Name);

			//暂不考虑不包含子节点的情况
			if(_filePath.EndsWith(".dll") && item.FirstChild.Name == "Reference")		//dll动态链接库
			{
				var tArr = _filePath.Split('\');
				string tValue = tArr[tArr.Length - 1].Replace(".dll", "");
				foreach(XmlNode child in item.ChildNodes)
				{
					foreach(XmlAttribute attr in child.Attributes)
					{
						if(attr.Name == "Include" && attr.Value == tValue)
						{
							return;
						}
					}
				}
				XmlElement tChild = (XmlElement)item.FirstChild.CloneNode(true);
				tChild.SetAttribute("Include", tValue);

				XmlElement tSubChild = tXmlDoc.CreateElement("HintPath", xmlNameSpace);
				tSubChild.InnerText = _filePath;

				tChild.AppendChild(tSubChild);
				item.AppendChild(tChild);
				if(!insertCountMap.ContainsKey(_csprojFilePath))
				{
					insertCountMap[_csprojFilePath] = 0;
				}
				insertCountMap[_csprojFilePath]++;
				break;
			}
			else if((_filePath.EndsWith(".cs") && item.FirstChild.Name == "Compile") || item.FirstChild.Name == "None")	//CS脚本或资源
			{
				//Debug.LogError(item.FirstChild.InnerText + "
" + item.FirstChild.Value);
				//foreach(XmlAttribute attr in item.FirstChild.Attributes)
				//{
				//	Debug.LogError(attr.Name + "	" + attr.Value);
				//}
				foreach(XmlNode child in item.ChildNodes)
				{
					foreach(XmlAttribute attr in child.Attributes)
					{
						if(attr.Name == "Include" && attr.Value == _filePath)
						{
							return;
						}
					}
				}
				XmlElement tChild = (XmlElement)item.FirstChild.CloneNode(true);
				//XmlElement tChild = tXmlDoc.CreateElement(item.FirstChild.Name, tNameSpace);
				tChild.SetAttribute("Include", _filePath);
				item.AppendChild(tChild);
				if(!insertCountMap.ContainsKey(_csprojFilePath))
				{
					insertCountMap[_csprojFilePath] = 0;
				}
				insertCountMap[_csprojFilePath]++;
				break;
			}
		}
		if(_syncSave)
		{
			tXmlDoc.Save(_csprojFilePath);    //todo:try-catch,防止IO异常

			xmlDocCacheMap.Remove(_csprojFilePath);
			xmlNodeLstCacheMap.Remove(_csprojFilePath);
		}
	}