josh's WebLog

Visual C++ IDE from the trenches

understanding the VC project system part V: building, tools and dependencies

okay, let's talk about building c++ projects. several things go into building a project: a list of files, the tools that are run, the switches to set on those tools and how we know when to execute those tools.

the files and the set of switches (properties) i've talked about before, see my earlier blog entries. build tools is a slightly more interesting subject. each command-line tool that is run as part of the build (cl.exe, link.exe, midl.exe et al) is wrapped internally by a COM object. (see Peter's blog for more information on our object model). this tool object knows all about its properties, how to convert them into switches, and where it runs in the build process. the overall build driver asks each tool when it should run, organizing the whole set of tools into a set of "buckets" based on their responses, and then calls the tools in each bucket, in order, to generate their command lines. it then executes those command-lines. so the overall build process looks roughly like this:
1) call each tool to find out what "bucket" it should execute in
2) sort the list of tools by "bucket"
3) for each tool:
3a) call the tool to generate its command-line
3b) start a new thread that executes that command-line

an important piece is still missing from this picture, however. we may know that the linker (link.exe) always has to run after the c++ compiler (cl.exe), but how do we know if the compiler needs to run at all? in order to know this we need to know if anything that the compiler tool depends on is out-of-date. to determine if this is the case we need to know the set of inputs to the compiler tool (i.e. the files that are going to be fed into it) and what its outputs are going to be. if an input file is newer than the corresponding output file, then clearly it is out-of-date and the compiler needs to be called on it. tools that don't have any out-of-date inputs don't run. the project system keeps a dependency graph of inputs and outputs which it walks to generate up the list of actual actions that need to be taken at build time.

all tools have these basic input dependencies, or what we call "source dependencies", but there is another kind of dependency that can make a file out-of-date. again, take the c++ compiler as an example. a c++ source file can include another file into itself using the c preprocessor's "#include" directive. if foo.cpp #include's foo.h, and foo.h is out-of-date with respect to the output (foo.obj, in this case) then foo.cpp needs to build. in the VC project system, determining these so-called "scanned dependencies" is up to the tool object, but they are kept track of by the engine's dependency graph, just like source dependencies.

the c++ compiler provides scanned dependency information in a database (stored in the .idb file) that the project system uses to create its list of scanned dependencies. because this information comes from the compiler itself, it is hardly ever wrong (i've only seen one bug in it in the 6+ years that i have worked on the project system). the idl and rc compilers do not however, and the project system calculates their scanned dependencies via a somewhat crude text-scan of the source files. it doesn't understand #define's at all, which causes a bug that often bites developers and causes their projects to always rebuild: if you #include a file which doesn't exist on disk, we mark the file out-of-date. because the file never exists, the #include'ing file is always out-of-date. normally this wouldn't be a problem because the file wouldn't build successfully anyway, but if the #include is inside a #define block that shouldn't be scanned, it might. this bug seems to account for 95% of the cases where a project always builds.
Published Friday, February 13, 2004 6:39 PM by josh_

Comments

 

J C Dickman said:

Your last paragraph describes my problem *exactly*! Thanks! - Having "upgraded" to VC7 I seem to have made one step forward and 5 steps back!! :-)

Just to clarify though, if I remove the #defined #includes, how to I cater for conditional compilation? ie my resource file was shared between a VC7 build and a WinCE build. Perhaps I should have two completely separate files?
June 11, 2004 2:15 AM
 

josh s WebLog understanding the VC project system part V building | work from home said:

June 16, 2009 9:20 AM
Anonymous comments are disabled

© 2009 Microsoft Corporation. All rights reserved. Terms of Use  |  Trademarks  |  Privacy Statement
Microsoft
Page view tracker