Visual Studio解决方案/多个项目:如何在多个C ++项目之间有效传播项目属性
我正在使用包含多个项目(大约30个)的Visual Studio 2005 C ++解决方案。
根据我的经验,维护项目的所有属性(即包括路径,lib路径,链接的lib,代码生成选项等)常常变得很烦人,因为我们经常必须单击每个项目才能执行以下操作:修改它们。
当我们具有多种配置(Debug,Release,Release 64位等)时,情况甚至更糟。
现实生活中的例子:
- 假设我们要使用一个新的库,并且需要将该库的包含路径添加到所有项目。我们将如何避免必须编辑每个项目的每个属性?
- 假设我们要测试驱动一个新版本的库(例如2.1beta版),以便需要快速更改一组项目的包含路径/库路径/链接库?
笔记:
- 我知道可以一次选择多个项目,然后右键单击并选择"属性"。但是,该方法仅适用于对于不同项目已经完全相同的属性:我们不能使用该方法向包含不同包含路径的一组项目添加包含路径。
- 我也知道可以全局修改环境选项("工具/选项/项目和解决方案/目录"),但是由于不能将其集成到SCM中,因此不能令人满意
- 我也知道可以将"配置"添加到解决方案中。这无济于事,因为它使另一组项目属性得以维护
- 我知道Codegear C ++ Builder 2009可以通过所谓的"选项集"为这种需求提供可行的解决方案,该选项集可以被多个项目继承(我同时使用Visual Studio和C ++ Builder,但我仍然认为C ++ Builder在某些方面相对而言比较困难到Visual Studio)
- 我希望有人会建议使用" autconf"(例如CMake),但是是否可以将vcproj文件导入这种工具?
解决方案
我认为我们需要调查属性文件,即* .vsprops(较旧)或者* .props(最新)
我们确实需要手动将属性文件添加到每个项目,但是一旦完成,我们将拥有多个项目,但是只有一个。[vs] props文件。如果更改属性,则所有项目都将继承新设置。
是的,我绝对建议我们使用CMake。 CMake是可以生成Studio项目文件的最佳工具(我想我实际上已经尝试了所有工具)。
我还遇到了将现有的.vcproj文件转换为CMakeLists.txt的问题,并且我编写了一个Ruby脚本来处理大部分转换工作。该脚本无法处理诸如构建后步骤之类的事情,因此需要进行一些调整,但是它将避免我们从.vcproj文件中提取所有源文件名的麻烦。
由于链接到静态运行时库,我经常需要做类似的事情。我写了一个程序为我做。它基本上会扫描我们提供的任何路径的所有子目录,并对找到的任何.vcproj文件进行ID。然后一一打开,将其修改并保存。由于我很少使用它,因此路径对其进行了硬编码,但是我认为我们可以根据自己的喜好对其进行调整。
另一种方法是认识到Visual Studio Project文件只是XML文件,并且可以使用我们喜欢的XML类进行操作。当有很多我不想输入的包含目录时,我已经使用C#的XmlDocument更新了包含目录。
我包括两个例子。我们将需要根据自己的需要对其进行修改,但是这些应该可以入门。
这是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());
}
这是Cversion。享受...
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;
}
}
}
根据建议,我们应该查看属性表(aka .vsprops文件)。
我在这里写了关于此功能的简短介绍。

