views:

5333

answers:

4

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, ...).

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?

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?
+15  A: 

I think you need to investigate properties files, ie *.vsprops

You do need to add the properties file manually to each project, but once that's done, you have multiple projects, but one .vsprops file. If you change the properties, all projects inherit the new settings.

quamrana
+2  A: 

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.

I also had the issue of converting existing .vcproj files into CMakeLists.txt, and I wrote a Ruby-script which 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.

JesperE
+5  A: 

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.

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 XmlDocument for updating the include directories when there were A LOT of include directories that I didn't want to type in. :)

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:

#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...

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;
        }
    }
}
Jere.Jones
Wow. An answer and code samples in two different languages. How can I not vote for this answer? Thanks for code. (Of course I'm assuming it works... :)
TMarshall
+4  A: 

As suggested, you should look at Property Sheets (aka .vsprops files).
I wrote a very short introduction to this feature here.

Xavier Nodet
Thanks, nice introduction to property sheet. It was useful to me.
Jean-Philippe Jodoin