Click here to Skip to main content
15,868,141 members
Articles / Programming Languages / Go

CPM - A C/C++ Package Manager

Rate me:
Please Sign up or sign in to vote.
5.00/5 (4 votes)
27 Nov 2021MIT4 min read 10.3K   72   6   3
Effective management tool for multiple libraries
Good project layout and a simple tool make it easy to maintain complex project hierarchies.

Introduction

Today, software development world has pretty much standardized on using Git as version control system. Unfortunately, when it comes to reusing code between different repositories, there is far less agreement on how it should be done. Just Google for Git submodules or Bitbucket subtrees and you will find references exhorting you to use one or the other and immediately after, other references warning you to avoid those as plague. After trying different solutions, I ended up with my own system that I described it in a previous tip (Eat Your Own Dogfood). It shows how you can organize multiple C/C++ projects using symbolic links. The feedback received was very positive.

Now I decided to take this one step forward and provide a tool that automates this process. The result is CPM (C Package Manager). It is a simple tool that does only a very specific job:

  • It builds a C/C++ project that depends on many other C/C++ libraries.

It doesn't do many other things including, but not limited to:

  • french fries
  • your bed in the morning
  • ... well, you get the idea :)

In using this tool, there are two parts: one is projects' layout that must follow a certain pattern and the 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 shown in the tip mentioned before.

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 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

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:

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

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

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, 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

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:

JavaScript
{ "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:

JavaScript
{ "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:

JavaScript
{ "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, there are no dependencies; only the build rules:

JavaScript
{ "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 and setup your DEV_ROOT environment variable, you just have to clone the topmost repository (super_App) and invoke the CPM utility with a command like:

cpm -v super_app

(The -v flag specifies verbose mode and lets you see the intermediate steps.)

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 maintain a moderately sized codebase with dependencies running up to 10 levels deep and this tool makes it easy to ensure that all required projects are up to date and built in the proper order. The program is written 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.

History

  • 26th November, 2021 - Initial version

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Canada Canada
Mircea is the embodiment of OOP: Old, Opinionated Programmer. With more years of experience than he likes to admit, he is always opened to new things, but too bruised to follow any passing fad.

Lately, he hangs around here, hoping that some of the things he learned can be useful to others.

Comments and Discussions

 
Praisegreat practice Pin
Southmountain27-Nov-21 9:25
Southmountain27-Nov-21 9:25 
GeneralMy vote of 5 Pin
tugrulGtx26-Nov-21 22:58
tugrulGtx26-Nov-21 22:58 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA26-Nov-21 21:04
professionalȘtefan-Mihai MOGA26-Nov-21 21:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.