[CMake] Bug #3218 - Automatic Visual Studio dependency generation

Manuel Klimek klimek at box4.net
Mon Jul 3 02:14:45 EDT 2006


Hi there,

Here's a got a completely reworked version of my patch.
For me this is a killer feature when working with Visual
Studio 2005 and cmake: It automatically generates
workspaces for projects in subdirectories that contain
all required visual studio projects to compile the project
in the subdirectory.
This is very useful for big projects where you have
many applications using a lot of common libraries, but you
don't want to use the toplevel workspace, because loading
time and intellisense can be a real pain.
Feedback welcome :-)

Cheers,
Manuel

PS: The patch is against cmake 2.4.2 but will probably work on HEAD, too

Index: cmGlobalVisualStudio71Generator.cxx
===================================================================
---
cmGlobalVisualStudio71Generator.cxx	(.../tags/cmake-2.4.2/Source/cmGlobalVisualStudio71Generator.cxx)	(revision
16548)
+++
cmGlobalVisualStudio71Generator.cxx	(.../trunk/cmake/Source/cmGlobalVisualStudio71Generator.cxx)	(revision
16548)
@@ -48,7 +48,7 @@
 void cmGlobalVisualStudio71Generator
 ::WriteSLNFile(std::ostream& fout,
                                                    cmLocalGenerator* root,
-                                                  
std::vector<cmLocalGenerator*>& generators)
+                                                  
std::vector<cmLocalGenerator*>& localGenerators)
 {
   // Write out the header for a SLN file
   this->WriteSLNHeader(fout);
@@ -64,6 +64,150 @@
   bool doneRebuildCache = false;
   bool donePackage = false;

+  std::vector<cmLocalGenerator*> generators = localGenerators;
+  if(!generators.empty())
+    {
+    // initialize used targets
+    // for every generator we'll store all targets in this
+    // generator we depend on
+    // we start by adding all targets in the generators we got
+    // as parameter to this method:
+    // (this adds cmake standard targets like INSTALL and ZERO_CHECK etc,
+    // too, which is used later to prevent totally unrelated projects
+    // from getting linked in by FindTarget with a standard target as
+    // argument)
+    std::vector< std::vector<cmTarget*> > usedTargets(generators.size());
+    for(unsigned int i=0; i<generators.size(); ++i)
+      {
+      cmMakefile *makefile = generators[i]->GetMakefile();
+      // add all targets to our "used target matrix"
+      for(cmTargets::iterator it=makefile->GetTargets().begin(); it !=
+            makefile->GetTargets().end(); ++it)
+        {
+        usedTargets[i].push_back(&it->second);
+        }
+      }
+
+    // calculate closure on library dependencies
+    // let's get the top generator; we need it to find
+    // targets anywhere in the tree if we're in a subdirectory
+    cmLocalGenerator *topGenerator = generators[0];
+    while(topGenerator->GetParent() != NULL)
+      {
+      topGenerator = topGenerator->GetParent();
+      }
+    // algorithm:
+    // for each target in the "used target matrix":
+    //   find all dependent targets:
+    //     if any new target is found, add it to the "used target matrix"
+    // loop until no new dependencies are found
+    bool changed = false;
+    do
+      {
+      changed = false;
+      // build closure
+      unsigned int generatorSize = generators.size();
+      for(unsigned int iGenerator=0; iGenerator<generatorSize; ++iGenerator)
+        {
+        for(unsigned int iTarget=0;
iTarget<usedTargets[iGenerator].size(); ++iTarget)
+          {
+          // find all targets this target depends on;
+          // use link libraries as well as utilities (for example
+          // executables that are needed for the build process)
+          std::vector<std::string> targetDependencies;
+            {
+            cmTarget::LinkLibraryVectorType::const_iterator j, jend;
+            j =
usedTargets[iGenerator][iTarget]->GetLinkLibraries().begin();
+            jend =
usedTargets[iGenerator][iTarget]->GetLinkLibraries().end();
+            for(;j!= jend; ++j)
+              {
+              targetDependencies.push_back(j->first);
+              }
+            }
+            {
+            std::set<cmStdString>::const_iterator j, jend;
+            j = usedTargets[iGenerator][iTarget]->GetUtilities().begin();
+            jend = usedTargets[iGenerator][iTarget]->GetUtilities().end();
+            for(;j!= jend; ++j)
+              {
+              targetDependencies.push_back(*j);
+              }
+            }
+          // for each target we depend on, check if this target is
+          // already in our "used target matrix"
+          for(unsigned int iDependency=0;
iDependency<targetDependencies.size();
+              ++iDependency)
+            {
+            bool foundTarget = false;
+            for(unsigned int gen=0; gen<usedTargets.size(); ++gen)
+              {
+              for(unsigned int k=0; k<usedTargets[gen].size() &&
+                  !foundTarget; ++k)
+                {
+                  if(usedTargets[gen][k]->GetName() ==
targetDependencies[iDependency])
+                  {
+                    foundTarget = true;
+                  }
+                }
+              }
+            // if it's not in our "used target matrix", enter it:
+            if(!foundTarget)
+              {
+              // find the target globally (as far as I understand,
+              // cmake doesn't support multiple targets with the
+              // same name in subdirectories, with the exception
+              // of some standard cmake targets (INSTALL, ZERO_CHECK ...)
+              // so FindTarget should always return one exact match;
+              cmTarget *dependency =
+                topGenerator->GetGlobalGenerator()->
+                FindTarget(NULL,targetDependencies[iDependency].c_str());
+              if(dependency)
+                {
+                // do we have to add the whole generator? Yes, since
+                // we just want to include the project files where the
+                // targets are built in our workspace
+                // but we'll only add the dependencies for the targets
+                // that depend on our main target
+                cmLocalGenerator* generator =
+                  dependency->GetMakefile()->GetLocalGenerator();
+                // let's see if we already have this generator
+                // perhaps we should do string comparison, not
+                // pointer comparison?
+                int entryGenerator = -1;
+                for(unsigned int k=0; k<generators.size() &&
+                    (entryGenerator == -1); ++k)
+                  {
+                  if(generators[k] == generator)
+                    {
+                    entryGenerator = k;
+                    }
+                  }
+                // if we don't find the generator the new target is
+                // in, we add the generator
+                if(entryGenerator == -1)
+                  {
+                  // we have to update the project map - if we don't
+                  // the target dependencies will not be written
+                  this->ProjectMap[generators[iGenerator]->
+                    GetMakefile()->GetProjectName()].push_back(generator);
+                  entryGenerator = generators.size();
+                  generators.push_back(generator);
+                  usedTargets.push_back(std::vector<cmTarget*>());
+                  }
+                // we know that this is a new target, otherwise we
+                // wouldn't end here - so just add the new target
+                usedTargets[entryGenerator].push_back(dependency);
+                // run again over the new "used target matrix"
+                changed = true;
+                }
+              }
+            }
+          }
+        }
+      }
+    while(changed);
+    }
+
   // For each cmMakefile, create a VCProj for it, and
   // add it to this SLN file
   unsigned int i;
@@ -294,10 +438,17 @@
                                               const char* dir,
                                               cmTarget& t)
 {
-  std::string d = cmSystemTools::ConvertToOutputPath(dir);
+  std::string d = dir;
+  d += "/";
+  d += dspname;
+  d += ".vcproj";
+  d = cmSystemTools::ConvertToOutputPath(d.c_str());
+  if(d[0] != '\"')
+    {
+    d = std::string("\"") + d + std::string("\"");
+    }
   fout << "Project(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \""
-       << dspname << "\", \""
-       << d << "\\" << dspname << ".vcproj\", \"{"
+       << dspname << "\", " << d << " ,\"{"
        << this->GetGUID(dspname) << "}\"\n";
   fout << "\tProjectSection(ProjectDependencies) = postProject\n";
   this->WriteProjectDepends(fout, dspname, dir, t);



More information about the CMake mailing list