Reputation: 495
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.
Upvotes: 3
Views: 520
Reputation: 6240
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.
Thread.h needs thread.o at link time
#ifdef __WAND__
dependency[thread.o]
target[name[thread.h] type[include]]
#endif
This file should only be compiled when Windows is the target platform
#ifdef __WAND__
target[name[thread.o] type[object] platform[;Windows]]
#endif
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
Upvotes: 0
Reputation: 114579
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.
Upvotes: 3
Reputation: 33126
This is not an easy problem. Just a couple of many things that make this difficult:
class Foo
is defined in foo.h
but implemented in foo_cotr_dotr.cpp
, foo_this_function.cpp
, and foo_that_function.cpp
.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.
Upvotes: 0