How to install two copies of a C++ library on Linux for development purposes

Often, library development goes hand by hand with the development of your own project: you need a feature, and you realize that the feature would fit better in the original library rather than in your own code. I am working on a Client for the Ion Beam Simulator IBSimu library for plasma simulation, and I started to modify the original IBSimu library when I noticed that the partial saving of particle trajectories was not yet implemented. But how to do it in a safe way?

Ideally, one would like to have:

  • a default installation of the original library, that must remain intact on your system (just in case you want to revert back to the original);
  • a second copy of the library that you can change at your will to develop your code – this second copy MUST coexist with the default installation and not create any cross-compatibility issue. If you do not want to use it, it must be transparent to the system.

Having two copies comes handy due to the process of trials and errors that many programmers follow to write code – or at least, that I follow. And if you don’t like the changes you made to the library, of if they are accepted and integrated in the official version, you can just delete the working copy of the library without worries.

The following steps were intended for the IBSimu library and the IBSimu Client that I am working on, but they can be applied every time you desire to make changes to a library that uses pkg-config on Linux.

Installing the original library as the default

First, you download and install the library just as instructed in the software manual. My advice is to download the library’s source code in a folder named after the Git tag and/or commit. I keep all the source code in the $HOME/src folder, where $HOME is my home directory, so in the case of IBSimu I would run:

git clone git://ibsimu.git.sourceforge.net/gitroot/ibsimu/ibsimu $HOME/src/ibsimu-1.0.6dev-4ff76e`

Where 4ff76e is the latest commit in the Git repository as of today. Proceed to install it as usual, remembering to run the ./reconf script to create the configure file, if available:

cd ~/src/ibsimu-1.0.6dev-4ff76e
./reconf
./configure
./make
./make install`

The compiled files of the library go in their default directories, that usually are /usr/local/include/ and /usr/local/lib/.

Installing the second copy of the library

To install a working copy of the library, I clone again the remote repository but this time to a directory named without the commitment or the version. If I want to contribute to the code of a library, I need my local repository to be synchronized with the library’s main repository, and the “latest” version and commitment change with time. So the directory name is stable, and I let Git do the updating and organizing of the version.

git clone git://ibsimu.git.sourceforge.net/gitroot/ibsimu/ibsimu $HOME/src/ibsimu

The code in $HOME/src/ibsimu (or whatever directory you decided for your library) is the code you want to work on.

The compiled files of this second copy of the library need to go in a different directory so that they do not overwrite the first copy. The default installation usually uses the /usr/local/lib/ and /usr/local/include/ directories, but for the second copy I put them in my home directory, under the $HOME/lib/ and $HOME/include/ibsimu-1.0.6dev/ subdirectories. To do so, you must run the ./configure script with the option –prefix=$HOME flag (or with whatever target directory you desire):

cd ~/src/ibsimu-1.0.6dev-4ff76e
./reconf
./configure --prefix=$HOME</code></pre>

Now, the first time you install it, and every time you work on the library source code, you simply run:

make
make install</code></pre>

And the compiled files go to the ~/lib/ and ~/include/ibsimu-1.0.6dev/ subdirectories.

Switching between the two copies

Now that you have a original and a developing versions of the library installed, the next step is, how do I tell the compiler to use one or the other?

The first, and most obvious way, is to link directly the libraries to the compiler, with the -I flag ( -I/usr/local/include/ibsimu-1.0.6dev vs. -I$HOME/include/ibsimu-1.0.6dev/) for the compiler, and the -L flag for the linker ( -L/usr/local/lib vs. -L~/lib). If you have a Makefile on your project, you can edit that file manually.

The drawback of that method is that it is tedious and error prone. Every time you sync your project with a public repository, you run the risk that the changes to your Makefile will be overwritten. Furthermore, if you are developing your own project to be distributed, you must have two versions of Makefile, one for your machine, and one to be published in your project. Not very handy.

But if the library you are linking uses the pkg-config library manager, there is a far easier solution.

The IBSimu library uses the pkg-config manager, so in the make file of the IBSimu Client there is the option `pkg-config --cflags ibsimu-1.0.6dev` passed to the compiler, that expands to:

-pthread -I/usr/local/include/ibsimu-1.0.6dev -I/usr/include/gtk-3.0 -I/usr/include/at-spi2-atk/2.0 -I/usr/include/at-spi-2.0 -I/usr/include/dbus-1.0 -I/usr/lib/x86_64-linux-gnu/dbus-1.0/include -I/usr/include/gtk-3.0 -I/usr/include/gio-unix-2.0/ -I/usr/include/cairo -I/usr/include/pango-1.0 -I/usr/include/harfbuzz -I/usr/include/pango-1.0 -I/usr/include/atk-1.0 -I/usr/include/cairo -I/usr/include/pixman-1 -I/usr/include/freetype2 -I/usr/include/libpng16 -I/usr/include/freetype2 -I/usr/include/libpng16 -I/usr/include/gdk-pixbuf-2.0 -I/usr/include/libpng16 -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include

and the option `pkg-config --libs ibsimu-1.0.6dev` for the linker, that expands to:

-L/usr/local/lib -libsimu-1.0.6dev -lfreetype -lfontconfig -lfreetype -lz -lpng16 -lz -lgsl -lgslcblas -lm -lgtk-3 -lgdk-3 -lpangocairo-1.0 -lpango-1.0 -latk-1.0 -lcairo-gobject -lcairo -lgdk_pixbuf-2.0 -lgio-2.0 -lgobject-2.0 -lglib-2.0

That means, every time you run the make command, that list of options are automatically added to g++ during compiling and linking. You could just as well remove the `pkg-config...` and copy the list of options in its place, and everything would compile and link just fine.

All those options are contained in a pkg-config .pc file that, by default, is in the /usr/local/lib/pkgconfig/ directory.

Note that those are options generated automatically by your system and are based on your system configuration, so on your machine they might be different. But on a standard Ubuntu machine, the include files (that is, the .h and .hpp with the definition of the available functions) are usually in the /usr/local/include/ibsimu-1.*/ directory, the libraries (that is, the .o or .so files with the actual executable code to link ) are in the /usr/local/lib/ directory, and the pkg-config .pc file are in the /usr/lib/pkgconfig , /usr/share/pkgconfig and /usr/local/lib/pkgconfig/. You can view the list of directories that pkg-config uses to search for packages by running the following command:

pkg-config --variable pc_path pkg-config

Your second installation of the library will have created its own .pc file under the $HOME/lib/pkgconfig/ directory (or whatever directory you chose).

You can switch between the two libraries just by setting the path of the pkg-manager to the development version of the library with the command:

export PKG_CONFIG_PATH=$HOME/lib/pkgconfig/

or by clearing the path variable to revert to the default version:

unset PKG_CONFIG_PATH

To make your development version of the library as the standard version, copying and pasting the .pc file from $HOME/lib/pkgconfig/ to /usr/local/lib/pkgconfig/ (or wherever is the original pkg-config .pc file of your library) is not a real solution, as the next time you compile the library, that file will be overwritten. It is safer to keep the two installations separated and set the PKG_CONFIG_PATH in a system wide configuration file.

C++ 

See also