Visual Studio Solutions / Multiple project:如何在多个C ++项目中有效地传播项目属性

我正在使用包含多个项目(约30)的Visual Studio 2005 C ++解决scheme。 根据我的经验,保持项目的所有属性(即包括path,libpath,链接的库,代码生成选项等)往往变得烦人,因为您经常需要单击每个项目才能修改它们。 当你有多个configuration(Debug,Release,Release 64位,…)时,情况会变得更糟。

真实的例子:

  • 假设你想使用一个新的库,并且你需要将包含这个库的path添加到所有的项目中。 你将如何避免编辑每个项目的每个属性?
  • 假设你想testing驱动器的新版本库(比如版本2.1beta),这样你需要快速更改一组项目的包含path/库path/链接库?

笔记:

  • 我知道可以一次select多个项目,然后右键单击并select“属性”。 但是,此方法仅适用于已经完全相同的不同项目的属性:不能使用它来为使用不同包含path的一组项目添加包含path。
  • 我也知道,可以在全球范围内修改环境选项(工具/选项/项目和解决scheme/目录),但这并不令人满意,因为它不能被集成到SCM
  • 我也知道可以添加“configuration”到解决scheme。 这并没有帮助,因为它使另一组项目属性来维护
  • 我知道Codegear C ++ Builder 2009通过所谓的“选项集”提供了一个可行的答案,这个选项集可以被几个项目所inheritance(我同时使用Visual Studio和C ++ Builder,而且我仍然认为C ++ Builder在某些方面比较困难到Visual Studio)
  • 我期望有人会build议像CMake的“autconf”,但是有可能导入vcproj文件到这样的工具?

我认为你需要调查属性文件,即* .vsprops (旧)或* .props (最新)

您需要手动添加属性文件到每个项目,但一旦完成,您有多个项目,但一个。[vs]道具文件。 如果更改属性,所有项目都会inheritance新的设置。

我经常需要做类似的事情,因为我链接到静态运行时库。 我写了一个程序为我做。 它基本上扫描你给它的任何path的所有子目录,并idsfind它find的任何.vcproj文件。 然后一个接一个地打开它们,修改它们并保存。 由于我只用它很less,path是硬编码的,但我认为你可以调整你喜欢的方式。

另一种方法是意识到Visual Studio项目文件只是XML文件,可以使用您最喜欢的XML类来操作。 我已经做了一些使用C#的XmlDocument更新包含目录时,有很多包括目录,我不想input。:)

我包括两个例子。 你将需要修改它们以满足自己的需求,但这些应该让你开始。

这是C ++版本:

 #include <stdio.h> #include <tchar.h> #include <iostream> #include <fstream> #include <string> #include <sstream> #include <vector> #include <boost/filesystem/convenience.hpp> #include <boost/filesystem/operations.hpp> #include <boost/filesystem/path.hpp> #include <boost/regex.hpp> #include <boost/timer.hpp> using boost::regex; using boost::filesystem::path; using namespace std; vector<path> GetFileList(path dir, bool recursive, regex matchExp); void FixProjectFile(path file); string ReadFile( path &file ); void ReplaceRuntimeLibraries( string& contents ); void WriteFile(path file, string contents); int _tmain(int argc, _TCHAR* argv[]) { boost::timer stopwatch; boost::filesystem::path::default_name_check(boost::filesystem::native); regex projFileRegex("(.*)\\.vcproj"); path rootPath("D:\\Programming\\Projects\\IPP_Decoder"); vector<path> targetFiles = GetFileList(rootPath, true, projFileRegex); double listTimeTaken = stopwatch.elapsed(); std::for_each(targetFiles.begin(), targetFiles.end(), FixProjectFile); double totalTimeTaken = stopwatch.elapsed(); return 0; } void FixProjectFile(path file) { string contents = ReadFile(file); ReplaceRuntimeLibraries(contents); WriteFile(file, contents); } vector<path> GetFileList(path dir, bool recursive, regex matchExp) { vector<path> paths; try { boost::filesystem::directory_iterator di(dir); boost::filesystem::directory_iterator end_iter; while (di != end_iter) { try { if (is_directory(*di)) { if (recursive) { vector<path> tempPaths = GetFileList(*di, recursive, matchExp); paths.insert(paths.end(), tempPaths.begin(), tempPaths.end()); } } else { if (regex_match(di->string(), matchExp)) { paths.push_back(*di); } } } catch (std::exception& e) { string str = e.what(); cout << str << endl; int breakpoint = 0; } ++di; } } catch (std::exception& e) { string str = e.what(); cout << str << endl; int breakpoint = 0; } return paths; } string ReadFile( path &file ) { // cout << "Reading file: " << file.native_file_string() << "\n"; ifstream infile (file.native_file_string().c_str(), ios::in | ios::ate); assert (infile.is_open()); streampos sz = infile.tellg(); infile.seekg(0, ios::beg); vector<char> v(sz); infile.read(&v[0], sz); string str (v.empty() ? string() : string (v.begin(), v.end()).c_str()); return str; } void ReplaceRuntimeLibraries( string& contents ) { regex releaseRegex("RuntimeLibrary=\"2\""); regex debugRegex("RuntimeLibrary=\"3\""); string releaseReplacement("RuntimeLibrary=\"0\""); string debugReplacement("RuntimeLibrary=\"1\""); contents = boost::regex_replace(contents, releaseRegex, releaseReplacement); contents = boost::regex_replace(contents, debugRegex, debugReplacement); } void WriteFile(path file, string contents) { ofstream out(file.native_file_string().c_str() ,ios::out|ios::binary|ios::trunc); out.write(contents.c_str(), contents.length()); } 

这是C#版本。 请享用…

 using System; using System.Collections.Generic; using System.Text; using System.Xml; using System.IO; namespace ProjectUpdater { class Program { static public String rootPath = "D:\\dev\\src\\co\\UMC6\\"; static void Main(string[] args) { String path = "D:/dev/src/co/UMC6/UMC.vcproj"; FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); XmlDocument xmldoc = new XmlDocument(); xmldoc.Load(fs); XmlNodeList oldFiles = xmldoc.GetElementsByTagName("Files"); XmlNode rootNode = oldFiles[0].ParentNode; rootNode.RemoveChild(oldFiles[0]); XmlNodeList priorNode = xmldoc.GetElementsByTagName("References"); XmlElement filesNode = xmldoc.CreateElement("Files"); rootNode.InsertAfter(filesNode, priorNode[0]); DirectoryInfo di = new DirectoryInfo(rootPath); foreach (DirectoryInfo thisDir in di.GetDirectories()) { AddAllFiles(xmldoc, filesNode, thisDir.FullName); } List<String> allDirectories = GetAllDirectories(rootPath); for (int i = 0; i < allDirectories.Count; ++i) { allDirectories[i] = allDirectories[i].Replace(rootPath, "$(ProjectDir)"); } String includeDirectories = "\"D:\\dev\\lib\\inc\\ipp\\\""; foreach (String dir in allDirectories) { includeDirectories += ";\"" + dir + "\""; } XmlNodeList toolNodes = xmldoc.GetElementsByTagName("Tool"); foreach (XmlNode node in toolNodes) { if (node.Attributes["Name"].Value == "VCCLCompilerTool") { try { node.Attributes["AdditionalIncludeDirectories"].Value = includeDirectories; } catch (System.Exception e) { XmlAttribute newAttr = xmldoc.CreateAttribute("AdditionalIncludeDirectories"); newAttr.Value = includeDirectories; node.Attributes.InsertBefore(newAttr, node.Attributes["PreprocessorDefinitions"]); } } } String pathOut = "D:/dev/src/co/UMC6/UMC.xml"; FileStream fsOut = new FileStream(pathOut, FileMode.Create, FileAccess.Write, FileShare.ReadWrite); xmldoc.Save(fsOut); } static void AddAllFiles(XmlDocument doc, XmlElement parent, String path) { DirectoryInfo di = new DirectoryInfo(path); XmlElement thisElement = doc.CreateElement("Filter"); thisElement.SetAttribute("Name", di.Name); foreach (FileInfo fi in di.GetFiles()) { XmlElement thisFile = doc.CreateElement("File"); String relPath = fi.FullName.Replace(rootPath, ".\\"); thisFile.SetAttribute("RelativePath", relPath); thisElement.AppendChild(thisFile); } foreach (DirectoryInfo thisDir in di.GetDirectories()) { AddAllFiles(doc, thisElement, thisDir.FullName); } parent.AppendChild(thisElement); } static List<String> GetAllDirectories(String dir) { DirectoryInfo di = new DirectoryInfo(dir); Console.WriteLine(dir); List<String> files = new List<String>(); foreach (DirectoryInfo subDir in di.GetDirectories()) { List<String> newList = GetAllDirectories(subDir.FullName); files.Add(subDir.FullName); files.AddRange(newList); } return files; } static List<String> GetAllFiles(String dir) { DirectoryInfo di = new DirectoryInfo(dir); Console.WriteLine(dir); List<String> files = new List<String>(); foreach (DirectoryInfo subDir in di.GetDirectories()) { List<String> newList = GetAllFiles(subDir.FullName); files.AddRange(newList); } foreach (FileInfo fi in di.GetFiles()) { files.Add(fi.FullName); } return files; } } } 

正如所build议的,你应该看看财产表(又名.vsprops文件)。
我在这里写了一个非常简短的介绍。

是的,我肯定会build议使用CMake 。 CMake是最好的工具(我想我已经尝试了所有这些工具),可以生成Studio项目文件。

我也有将现有的.vcproj文件转换为CMakeLists.txt的问题,并且我写了一个Ruby脚本来处理大部分的转换。 该脚本不处理像构build后的步骤等事情,所以一些调整是必要的,但它会为您节省从.vcproj文件中提取所有源文件名的麻烦。

* .vcxproj文件是msbuild文件。 所以你只需要一个你不想要的财产在你的项目文件,并删除它。 然后把它放在你的财产表。 然后确保所有项目文件正确导入该属性表。

这对于数百个文件来说可能是令人难以置信的繁琐。 我写了一个工具使这个互动:

https://github.com/chris1248/MsbuildRefactor