Question

I'm trying to create simple C++ incremental-build tool with dependency resolver. I've been confused about one problem with cpp build process. Imagine we have a library consists several files:

// h1.h
void H1();

// s1.cpp
#include "h1.h"
#include "h2.h"
void H1(){ H2(); }

// h2.h
void H2();

// s2.cpp
#include "h2.h"
#include "h3.h"
void H2(){ /*some implementation*/ }
void H3(){ /*some implementation*/ }

// h3.h
void H3();

When in client code including h1.h

// app1.cpp
#include "h1.h"
int main()
{
  H1();
  return 0;
}

there is implicit dependency of s2.cpp implementation: our_src -> h3 -> s1 -> h2 -> s2. So we need to link with two obj files:

g++ -o app1 app1.o s1.o s2.o

In contrast when h3.h included

// app2.cpp
#include "h3.h"
int main()
{
  H3();
  return 0;
}

there is only one source dependency: our_src -> h3 -> s2

So when we include h3.h we need only s2.cpp compiled (in spite of s1.cpp -> h2.h inclusion):

g++ -o app2 app2.o s2.o

This is very simple example of the problem, in real projects surely we may have several hundreds files and chains of inefficient includes may contain much more files.

So my question is: Is there a way or instruments to find out which header inclusion could be omitted when we check dependencies (without CPP parsing)?

I would appreciate for any responce.

Was it helpful?

Solution

In the case you stated to see the implicit dependence on s2.cpp you need to parse the implementation module s1.cpp because only there you will find that the s1 module is using s2. So to the question "can I solve this problem without parsing .cpp files" the answer is clearly a no.

By the way as far as the language is concerned there is no difference between what you can put in an header file or in an implementation file. The #include directive doesn't work at the C++ level, it's just a textual macro function without any understanding of the language. Moreover even parsing "just" C++ declarations is a true nightmare (the difficult part of C++ syntax are the declarations, not the statements/expressions).

May be you can use the result of gccxml that parses C++ files and returns an XML data structure that can be inspected.

OTHER TIPS

This is not an easy problem. Just a couple of many things that make this difficult:

  1. What if one header file is implemented in N>1 source files? For example, suppose class Foo is defined in foo.h but implemented in foo_cotr_dotr.cpp, foo_this_function.cpp, and foo_that_function.cpp.
  2. What if the same capability is implemented in multiple source files? For example, suppose Foo::bar() has implementations in foo_bar_linux.cpp, foo_bar_osx.cpp, foo_bar_sunos.cpp. The implemention to be used depends on the target platform.

One easy solution is to build a shared or dynamic library and link against that library. Let the toolchain resolve those dependencies. Problem #1 disappears entirely, and problem #2 does too if you have a smart enough makefile.

If you insist on bucking this easy solution you are going to need to do something to resolve those dependencies yourself. You can eliminate the above problems (not an exhaustive list) by a project rule one header file == one source file. I have seen such a rule, but not nearly as often as I've seen a project rule that says one function == one source file.

You may have a look at how I implemented Wand. It uses a directive to add dependencies for individual source files. The documentation is not fully completed yet, but there are examples of Wand directives in the source code of Gabi.

Examples

Thread class include file

Thread.h needs thread.o at link time

#ifdef __WAND__
dependency[thread.o]
target[name[thread.h] type[include]]
#endif

Thread class implementation on windows (thread-win32.cpp)

This file should only be compiled when Windows is the target platform

#ifdef __WAND__
target[name[thread.o] type[object] platform[;Windows]]
#endif

Thread class implementation on GNU/Linux (thread-linux.cpp)

This file should only be compiled when GNU/Linux is the target platform. On GNU/Linux, the external library pthread is needed when linking.

#ifdef __WAND__
target
    [
    name[thread.o] type[object] platform[;GNU/Linux]
    dependency[pthread;external]
    ]
#endif

Pros and cons

Pros

  • Wand can be extended to work for other programming languages
  • Wand will save all necessary data needed to successfully link a new program by just giving the command wand
  • The project file does not need to mention any dependencies since these are stored in the source files

Cons

  • Wand requires extra directives in each source file
  • The tool is not yet widely used by library writers
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top