Eat Your Own Dogfood

Eat Your Own Dogfood #

Introduction #

Any larger project involves code from different sources. When reusing our own code or downloading something from GitHub we have to fight with different layouts of source code, include files and libraries. This tip wants to show a way to ease this pain. It is the result of many wounds acquired over many years of practice and many of those were self-inflicted. One more thing: this is not a universal panacea; it applies to C/C++ projects and works even better for those using VisualStudio.

We will consider a common situation where we have developed two libraries cool_A and cool_B that need to be reused in an application SuperApp.

Library code layout #

For each library we have include files, source files and we produce a static link library. The general code layout is:


So far, not much different from what you already know except for the following:

RULE 1 - Include files that need to be visible to users are placed in a subfolder of the include folder. The subfolder has the same name as the library.

If users of cool_A have managed to have cool_A/include on the include path (we will see in a moment how), they can refer to hdr1.h file like this:

#include <cool_A/hdr1.h>

The advantage is that it avoids name clashes between different libraries. In our case, if a program uses both cool_A and cool_B, the corresponding include directives will be:

#include <cool_A/hdr1.h>
#include <cool_B/hdr1.h>

Note that I didn’t say anything about the output library. I will get to that soon.

Windows users are not so much accustomed with symbolic links as they showed up rather late in the Windows world. They can however bring significant benefits for managing multiple projects.

Following the structure shown before, our application that uses cool_A and cool_B will have an include folder but in this folder we will place symbolic links to cool_A and cool_B include folders. The folder structure will look something like this (symbolic links are shown in blue):
To create those symbolic links, assuming your current directory is SuperApp\include you have to issue the following commands:

mklink /d cool_A \DevTreeRoot\cool_A\include\cool_A
mklink /d cool_B \DevTreeRoot\cool_B\include\cool_B

With the magic of symbolic links, SuperApp needs to have only its include folder mentioned in the include path and all the other libraries used will automatically become available.

We can now use the same trick for static link libraries. By convention, we will put them in the lib folder. This time however we are going to place the lib folder at the root of development tree and place symbolic links in each project that uses it. Without repeating the parts already shown of the files layout, here is the part related to lib folder (again, symbolic links are shown in blue):

RULE2: The static link libraries folder, lib exists at the root of development tree and it is just made visible through symbolic links in each project that uses it.

If there are different flavors of link libraries (debug, release, 32-bit, 64-bit) they can be accommodated as subfolders of the lib folder.

Automation #

Creating the symbolic links required by a project can be automated with a simple batch file. I use the name mklinks.bat in all my projects and here is how it would look for SuperApp:

rem Make sure DEV_TREE_ROOT environment variable is defined

:MAKELINKS
if not exist lib\nul mklink /d lib %DEV_TREE_ROOT%\lib

pushd "%~dp0include"

if not exist cool_A\nul     mklink /d cool_A %DEV_TREE_ROOT%\cool_A\include\cool_A
if not exist cool_B\nul     mklink /d cool_B %DEV_TREE_ROOT%\cool_B\include\cool_B

popd

Integration with VisualStudio #

This scheme can be easily applied to any C/C++ VisualStudio project. There are just a few configurations to apply to a project:

  1. Set include path to (or add to it) $(SolutionDir)include
  2. For any library set the output path to $(SolutionDir)lib\$(PlatformTarget)\$(Configuration)\
  3. For any application (or DLL) set library path to $(SolutionDir)lib\$(PlatformTarget)\$(Configuration)

Final notes #

  • Some libraries can depend on other libraries. In our example cool_B might use the cool_A library. In that case it would just need a symbolic link to cool_B include folder.

  • Two more recommendations for where to place various files in VisualStudio projects. They have shown to scale well even with complicated project hierarchies:

    1. Set the intermediate files directory to $(SolutionDir)o\$(ProjectName)\$(PlatformTarget)\$(Configuration)\
    2. For any application (or DLL) set the output path to $(SolutionDir)app\$(ProjectName)\$(PlatformTarget)\$(Configuration)\

Using the sample code #

First of all, the code is neither “cool” nor “Super”. It is just some demo code intended to show the benefits of following these rules. Download it and follow these steps:

  • run mklinks.bat scripts in all projects (cool_A, cool_B, SupperApp)
  • build the libraries cool_A and cool_B
  • build the application SuperApp

Conclusions #

Using symbolic links and a few simple rules makes code reuse a lot easier. The different settings used in VisualStudio are the result of many years of refinement until I’ve got everything right. As an example, the recommendation to set the intermediate files directory to $(SolutionDir)o\$(ProjectName)\$(PlatformTarget)\$(Configuration)\ is based on a few goals:

  • it is nice to have all object files in one place to be able to clean a project easily, hence the common o folder
  • you might have multiple projects in one solution and their intermediate files need to be kept separate, hence the $(ProjectName) part
  • for sure each “flavor” of build (x86, x64, debug, release, etc.) needs to be separate, hence the $(PlatformTarget)\$(Configuration) part.

This system works very well for different Git repositories where you can just fetch the code from each repository and combine them using symbolic links.

History #

  • October 7th, 2021 - Initial version