visual-studio Visual Studio 解决方案/多项目:如何在多个 C++ 项目之间有效地传播项目属性
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/111631/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Visual Studio Solutions / Multiple project : How to effectively propagate project properties amongst several C++ projects
提问by Pascal T.
I am working with a Visual Studio 2005 C++ solution that includes multiple projects (about 30). Based upon my experience, it often becomes annoying to maintain all the properties of the projects (i.e include path, lib path, linked libs, code generation options, ...), as you often have to click each and every project in order to modify them. The situation becomes even worse when you have multiple configurations (Debug, Release, Release 64 bits, ...).
我正在使用包含多个项目(大约 30 个)的 Visual Studio 2005 C++ 解决方案。根据我的经验,维护项目的所有属性(即包括路径、lib 路径、链接的库、代码生成选项等)通常很烦人,因为您经常必须单击每个项目才能修改它们。当您有多个配置(调试、发布、发布 64 位,...)时,情况会变得更糟。
Real life examples:
现实生活中的例子:
- Assume you want to use a new library, and you need to add the include path to this library to all projects. How will you avoid to have to edit the properties of each an every project?
- Assume you want to test drive a new version of library (say version 2.1beta) so that you need to quickly change the include paths / library path / linked library for a set of projects?
- 假设你想使用一个新的库,你需要将这个库的包含路径添加到所有项目中。您将如何避免必须编辑每个项目的属性?
- 假设您要测试驱动新版本的库(比如 2.1beta 版),以便您需要快速更改一组项目的包含路径/库路径/链接库?
Notes:
笔记:
- I am aware that it is possible to select multiple projects at a time, then make a right click and select "properties". However this method only works for properties that were already exactly identical for the different projects : you can not use it in order to add an include path to a set of project that were using different include path.
- I also know that it is possible to modify globally the environment options (Tools/Options/Project And solutions/Directories), however it is not that satisfying since it can not be integrated into a SCM
- I also know that one can add "Configurations" to a solutions. It does not helps since it makes another set of project properties to maintain
- I know that codegear C++ Builder 2009 offers a viable answer to this need through so called "Option sets" which can be inherited by several projects (I use both Visual Studio and C++ Builder, and I still thinks C++ Builder rocks on certain aspects as compared to Visual Studio)
- I expect that someone will suggest an "autconf" such as CMake, however is it possible to import vcproj files into such a tool?
- 我知道可以一次选择多个项目,然后右键单击并选择“属性”。但是,此方法仅适用于对于不同项目已经完全相同的属性:您不能使用它来将包含路径添加到使用不同包含路径的一组项目中。
- 我也知道可以全局修改环境选项(工具/选项/项目和解决方案/目录),但是它不是那么令人满意,因为它不能集成到 SCM 中
- 我也知道可以将“配置”添加到解决方案中。它没有帮助,因为它需要维护另一组项目属性
- 我知道 codegear C++ Builder 2009 通过所谓的“选项集”为这个需求提供了一个可行的答案,它可以被多个项目继承(我同时使用 Visual Studio 和 C++ Builder,我仍然认为 C++ Builder 在某些方面比较到 Visual Studio)
- 我希望有人会建议使用“autconf”,例如 CMake,但是是否可以将 vcproj 文件导入到这样的工具中?
采纳答案by quamrana
I think you need to investigate properties files, i.e. *.vsprops(older) or *.props(latest)
我认为您需要调查属性文件,即*.vsprops(较旧)或*.props(最新)
You doneed to add the properties file manually to each project, but once that's done, you have multiple projects, but one .[vs]props file. If you change the properties, all projects inherit the new settings.
您确实需要将属性文件手动添加到每个项目,但是一旦完成,您将拥有多个项目,但只有一个 .[vs]props 文件。如果更改属性,所有项目都会继承新设置。
回答by Jere.Jones
I often need to do something similar since I link to the static runtime libraries. I wrote a program to do it for me. It basically scans all of the subdirectories of whatever path you give it and ids any .vcproj files it finds. Then one by one, it opens them modifies them and saves them. Since I only use it rarely, the path is hard coded it, but I think you'll be able to adjust it how you like.
我经常需要做类似的事情,因为我链接到静态运行时库。我写了一个程序来为我做这件事。它基本上会扫描您提供的任何路径的所有子目录,并识别它找到的任何 .vcproj 文件。然后一一打开它们,修改它们并保存它们。由于我很少使用它,因此路径是硬编码的,但我认为您可以根据自己的喜好进行调整。
Another approach is to realize that Visual Studio Project files are simply XML files and can be manipulated with your favorite XML class. I've done something using C#'s XmlDocumentfor updating the include directories when there were A LOTof include directories that I didn't want to type in. :)
另一种方法是认识到 Visual Studio 项目文件只是 XML 文件,可以使用您喜欢的 XML 类进行操作。XmlDocument当有很多我不想输入的包含目录时,我已经使用 C#来更新包含目录。:)
I'm including both examples. You will need to modify them to your own needs, but these should get you started.
我包括两个例子。您需要根据自己的需要修改它们,但这些应该可以帮助您入门。
This is the C++ version:
这是 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());
}
This is the C# version. Enjoy...
这是 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;
}
}
}
回答by Xavier Nodet
回答by JesperE
Yes, I'd definitely suggest using CMake. CMake is the best tool (I think I've actually tried them all) which can generate Studio project files.
是的,我绝对建议使用CMake。CMake 是最好的工具(我想我实际上已经尝试了所有这些),它可以生成 Studio 项目文件。
I also had the issue of converting existing .vcproj files into CMakeLists.txt, and I wrote a Ruby-scriptwhich takes care of most of the conversion. The script doesn't handle things like post-build steps and such, so some tweaking is necessary, but it will save you the hassle of pulling all the source file names from the .vcproj files.
我还遇到了将现有 .vcproj 文件转换为 CMakeLists.txt 的问题,我编写了一个Ruby 脚本来处理大部分转换。该脚本不处理诸如构建后步骤之类的事情,因此需要进行一些调整,但它会为您省去从 .vcproj 文件中提取所有源文件名的麻烦。
回答by C Johnson
*.vcxproj files are msbuild files. So you just take a property you don't want in all your project files and delete it. Then put it in your property sheet. Then make sure all the projects files properly import that property sheet.
*.vcxproj 文件是 msbuild 文件。因此,您只需在所有项目文件中删除一个您不想要的属性并将其删除。然后把它放在你的属性表中。然后确保所有项目文件正确导入该属性表。
This can be incredibly tedious for hundreds of files. I wrote a tool make this interactive:
对于数百个文件来说,这可能非常乏味。我写了一个工具使这个互动:

