93

What's a good directory structure for larger C++ projects using Makefile ?

This is how my directory structure looks at the moment:

lib/ (class implementations *.cpp)
include/ (class definitions *.h)
tests/ (main.cpp for quick tests)

Now, I'm not sure how my Makefile should look like... it doesn't seem to work when .cpp files and .h files aren't in the same directory. Could anyone point me to a common directory structure with an accompanying Makefile so that I don't reinvent the wheel ?

Olivier Lalonde
  • 19,423
  • 28
  • 76
  • 91
  • Do you have a Makefile currently? If so, you might consider posting it to get specific feedback – Sam Post Mar 02 '10 at 04:15
  • use the Make VPATH=../lib:./include will make the header available in your tests directory – Kemin Zhou Oct 26 '16 at 03:46
  • 6.S096 MIT course suggests [this structure](https://web.mit.edu/6.s096/www/lecture/lecture07/lecture-07.pdf#page=14) (pages 14, 15) – amordo Sep 10 '22 at 19:31

7 Answers7

86

Separating the .cpp of the .h file is not always a good solution. Generally I separate both of them when it is used as a library (public header in include and private header with the source code).

If it is a library, this structure is ok.

lib/ (class implementations *.cpp .h)
include/ (class definitions *.h) <- Only those to be installed in your system
tests/ (main.cpp for quick tests)
doc/ (doxygen or any kind of documentation)

If it is a application

src/ (source for the application)
lib/ (source for the application library *.cpp *.hpp)
include/ (interface for the library *.h)
tests/ (main.cpp for quick tests) <- use cppunit for this part
doc/ (doxygen or any kind of documentation)

Use the flag -I$(PROJECT_BASE)/include to specify the include path for the compilation

If it is a big project, it can be good to use tool like autoconf/automake or cmake to build everything. It will ease the development.

Phong
  • 6,600
  • 4
  • 32
  • 61
  • 17
    I don't get it. What is the difference between `src` and `lib`? – feeela Feb 20 '13 at 16:03
  • 5
    When a application is too large, You want to separate your program in different module. Module that can be independant and you can reuse in other application. So you could write it like that lib/LibraryForLogging, lib/LibraryForDatabase etc... It is easier to manage than if it was mixed with all the source code. – Phong Feb 21 '13 at 06:02
  • This seems like a neat way to do it, but what about output (built files) folders? Let's say I want to build a library (without installing it), what folder should I put it in? – birgersp Jul 01 '16 at 10:29
  • 2
    Actually now that i think about it I would use different folder for library: library source code in `src` folder and library / object file in `lib` / `build` folder look appropriate. After it depend on your build system. make install copy content of include / lib into `$(install_folder)/include` and `$(install_folder)/lib` – Phong Jul 08 '16 at 01:37
41

For those who find this question after 2020, an alternative modern and reasoned vision of "Canonical Project Structure" for C++ has been presented by Boris Kolpackov: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1204r0.html

Briefly - no include/ and src/ split. All headers, sources, modules and unit tests go into one directory. Implementation details may be separated from public API by moving to <name>/<name>/details/ subdirectory.

<name>/
├── <name>/
│   ├── headers...
│   ├── sources...
│   ├── modules...
│   └── unit tests...
└── tests/
    ├── functional_test1/
    ├── functional_test2/
    ├── integration_test1/
    ├── integration_test2/
    └── ...

For example:

bestlib/
├── bestlib/
│   ├── foo.h
│   ├── foo.cpp
│   ├── foo.test.cpp
│   ├── bar.h
│   ├── bar.cpp
│   └── bar.test.cpp
└── tests/
    ├── functional_test1/
    └── integration_test1/
4LegsDrivenCat
  • 1,247
  • 1
  • 15
  • 24
  • 3
    Where should I put the external libraries binaries and header files? – Overflow Dec 13 '21 at 16:44
  • 2
    @Overflow We build and install them separately, not under the project dir. We use CMake's `find_package` function to find them. If it's installed not in the system but in some other directory, then you can set `XYZ_ROOT` variable before calling `find_package` to find XYZ library. – 4LegsDrivenCat Dec 15 '21 at 15:08
  • 1
    Why are `unit tests` not under `tests`? – Jeppe May 05 '22 at 19:34
  • 2
    @Jeppe You can find argumentation for that in "7.1 Unit Tests" by the link I provided in the answer. Briefly, it's convenient to have all files of some module together. If you work on some module you don't need to hop over different folders. For example, foo.h, foo.cpp, foo.test.cpp. – 4LegsDrivenCat May 06 '22 at 13:32
19

If you have many source files, it may also be a good idea to further subdivide your source directory. For instance, one subdirectory for the core functionality of your application, one for the GUI, etc.

src/core
src/database
src/effects
src/gui
...

Doing so also forces you to avoid unneeded relationships between your "modules", which is a prerequisite to nice and reusable code.

Gnurou
  • 7,923
  • 3
  • 22
  • 32
  • 1
    I suggest not to use `src` at all, if you have subprojects, because you will have to deal with an unnecessary indirection throughout the lifetime of your project, which sums up to quite a lot of unnecessary `cd src` or clicking on `src`. Just keep the generated files separate, otherwise go for as flat as the project logic allows. – Roland Puntaier Nov 18 '18 at 09:23
  • 1
    Facing this issue now. If the project is big, the inclusion tree maintenance is overwhelming overtime. – Sam Jul 10 '19 at 08:13
14

This is an old question. But you can consider the Pitchfork Project as a general guide.

https://github.com/vector-of-bool/pitchfork for the project.

Some Documentation here

kervin
  • 11,672
  • 5
  • 42
  • 59
  • This is actually great! What it is: somebody wrote a spec for C++ project layout and called it "The Pitchfork Layout (PFL)" :D – tanius Jun 18 '20 at 20:52
13

There is no one specific or required directory structure.

You can set it up anyway you like. Your problem is simple to solve. Just instruct Makefile to look into subdirectories or put compiled objects into subdirectories instead of using just current directory.

You would just use in Makefile paths:

%.o : %.cpp

replace with

bin/%.o : %.cpp

So it will check if binary file in directory bin exists and so on, you can apply the same to locations where files are compiled.

There are ways to add/remove/modify paths of source and object files.

Have a look at gnu make manual, specifically section 8.3 Functions for File Names,and the one before that 8.2 Functions for String Substitution and Analysis.

You can do stuff like:

get a list of objects from list of source files in current directory:

OBJ     = $(patsubst %.cpp, %.o, $(wildcard *.cpp))

Output:

Application.o Market.o ordermatch.o

If binary objects are in subdirectory bin but source code is in current directory you can apply prefix bin to generated object files:

OBJ     = $(addprefix bin/,$(patsubst %.cpp, %.o, $(wildcard *.cpp)))

Output:

bin/Application.o bin/Market.o bin/ordermatch.o

And so on.

stefanB
  • 77,323
  • 27
  • 116
  • 141
9

There is no "good directory structure". Pick a structure you're comfortable with and stick to it. Some like placing source files (headers and implementation files) in a src/ directory, so the root directory of the project has nothing but a makefile, a readme and little else. Some like placing helper libraries under a lib/ directory, unittests under test/ or src/test/, documentation under doc/ etc.

I have yet to hear of anyone splitting header files and implementation files into two distinct directories though. Personally I don't like splitting files into directories much. I usually place all my source in a single directory and all the documentation in another directory. If I rely on good search tools anyway, there's no need for a complex directory structure.

make can deal with the sort of structure where the makefile resides in a different directory than the source. The only thing is that it will invoke the rules from the directory of the makefile -- compilers usually have no problem compiling source that is in some subdirectory. You don't have to specify relative paths in your #includes; just specify the include path with compiler flags (gcc's -I flag etc).

wilhelmtell
  • 57,473
  • 20
  • 96
  • 131
4

If you haven't seen it before read Recursive Make Considered Harmful.

Short, short version: Though very common the recursive make idiom is less than optimal and gets ever worse as projects grow larger and more complicated. An alternative is presented.

Related link: What is your experience with non-recursive make?

Community
  • 1
  • 1
dmckee --- ex-moderator kitten
  • 98,632
  • 24
  • 142
  • 234