Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C

Eat Your Own Dogfood

5.00/5 (12 votes)
7 Oct 2021CPOL4 min read 11.1K   60  
Source code organization suggestion
How to use symbolic links for an easier code reuse between different projects

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 Visual Studio.

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. Here is some ASCII art showing the general code layout:

DevTreeRoot
   |
   +-- cool_A
   |    |
   |    +-- include
   |    |      |
   |    |      +-- cool_A
   |    |            |
   |    |            +-- hdr1.h
   |    |            |
   |    |            +-- hdr2.h
   |    +-- src
   |    |    |
   |    |    +-- file1.cpp
   |    |    |
   |    |    +-- file2.cpp
   |    +-- project file (cool_A.vcxproj) and other stuff
   |
   +-- cool_B
        |
        +-- include
        |      |
        |      +-- cool_B
        |            |
        |            +-- hdr1.h
        |            |
        |            +-- hdr4.h
        +-- src
        |    |
        |    +-- file1.cpp
        |    |
        |    +-- file2.cpp
        |
        +-- project file (cool_B.vcxproj) and other stuff

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:

C++
#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:

C++
#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.

Using Symbolic Links for Fun and Profit

Windows users are not so accustomed to 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 (angle brackets denote symbolic links):

DevTreeRoot
  |
  +-- SuperApp
  |      |
  |      +-- include
  |      |     |
  |      |     +-- <cool_A>
  |      |     |      |
  |      |     |      +-- hdr1.h
  |      |     |      |
  |      |     |      +-- hdr2.h
  |      |     |
  |      |     +-- <cool_B>
  |      |     |      |
  |      |     |      +-- hdr1.h
  |      |     |      |
  |      |     |      +-- hdr4.h
  |      |     other header files
  |      |
  |      +-- src
  |      |    |
  |      |    +-- source files
  |      other files
  ...

To create those symbolic links, assuming your current directory is SuperApp\include, you have to issue the following commands:

BAT
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, angle brackets denote symbolic links):

DevTreeRoot
  |
  +-- cool_A
  |     |
  |    ...
  |     +-- <lib>
  |           |
  |           all link libraries are here
  +-- cool_B
  |     |
  |    ...
  |     +-- <lib>
  |           |
  |           all link libraries are here
  +-- SuperApp
  |      |
  |     ...
  |      +-- <lib>
  |            |
  |            all link libraries are here
  +-- lib
       |
       all link libraries are here

RULE 2: The static link libraries folder, lib exists at the root of development tree and it is 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:

BAT
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 Visual Studio

This scheme can be easily applied to any C/C++ Visual Studio 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 Visual Studio 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 Visual Studio 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

  • 7th October, 2021 - Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)