CPM - A C/C++ Package Manager #
If I have convinced you to use the code layout presented in Eat Your Own Dogfood to easily organize multiple C/C++ projects using symbolic links, now it is time to take this one step forward to provide a tool that automates this process. The result is CPM (C Package Manager).
In using this tool there are two parts: one is projects layout that must follow a certain pattern and second is to describe projects’ dependency tree using a small JSON file for each project.
As an example, let us consider two libraries cool_A
and cool_B
that need to be used in an application super_App
. Both cool_A
and cool_B
use code from another library utils
. Each one for these has its own Git repository.
Project Layout #
You must adhere to the principles mentioned before. You can find a more formal description in Dogfood Layout
RULE 1 - All projects have their own folder and all project folders are in one parent folder. The environment variable
DEV_ROOT
points to this root of development tree.
Here a diagram showing the general code layout:
RULE 2 - 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
can refer to hdr1.h
file like this:
#include <cool_A/hdr1.h>
An additional advantage of this organization is that it prevents name clashes between different libraries. In this 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>
RULE 3 - Include folders of dependent modules are made visible through symbolic links
In the structure shown before, the application that uses cool_A
and cool_B
will have an include
folder but in this folder there are symbolic links to cool_A
and cool_B
include folders. The folder structure will look something like this (blue items in angle brackets denote symbolic links):
RULE 4 - All libraries reside in a
lib
folder at the root of development tree. Each module contains a symbolic link to this folder.
Without repeating the parts already shown of the files layout, here is the part related to lib
folder (again, blue and angle brackets denote symbolic links):
If there are different flavors of link libraries (debug, release, 32-bit, 64-bit) they can be accommodated as subfolders of the lib
folder.
Dependency Description Files #
To describe the relationship between projects, each project uses a file called CPM.JSON
.
The CPM.JSON
in super_App
folder has the following content:
{ "name": "super_App", "git": "git@github.com:user/super_App.git",
"depends": [
{"name": "cool_A", "git": "git@github.com:user/cool_A.git"},
{"name": "cool_B", "git": "git@github.com:user/cool_B.git"}
],
"build": [
{"os": "windows", "command": "msbuild", "args": ["super_app.proj"]},
{"os": "linux", "command": "cmake"}
]
}
For each dependent project, there is a line describing the dependent and giving the address of the Git repository. The build section specifies the command used to build the package for each operating system.
Similarly, the descriptor in cool_A
folder looks like this:
{ "name": "cool_A", "git": "git@github.com:user/cool_A.git",
"depends": [
{"name": "utils", "git": "git@github.com:user/utils.git"},
],
"build": [
{"os": "windows", "command": "msbuild", "args": ["cool_a.proj"]},
{"os": "linux", "command": "cmake"}
]
}
And in cool_B
:
{ "name": "cool_B", "git": "git@github.com:user/cool_B.git",
"depends": [
{"name": "utils", "git": "git@github.com:user/utils.git"},
],
"build": [
{"os": "windows", "command": "msbuild", "args": ["cool_b.proj"]},
{"os": "linux", "command": "cmake"}
]
}
Finally, in utils
:
{ "name": "utils", "git": "git@github.com:user/utils.git",
"build": [
{"os": "windows", "command": "msbuild", "args": ["utils.proj"]},
{"os": "linux", "command": "cmake"}
]
}
Operation #
Once you have created the dependency description files, you just have to clone the topmost repository (super_App
) and invoke the CPM utility with a command like:
cpm -v super_app
It proceeds to read the CPM.JSON
file and recursively fetches each dependent package and creates the required symbolic links. It then initiates the building of each dependent package starting with those at the bottom of the dependency tree.
Final Thoughts #
I wrote this program in Go because I was just learning Go and what better way to learn a new language than to use it for a small project. It can be downloaded from its GitHub repository as a precompiled binary for Windows or Linux or you can compile it from the attached source.