Tanishq Rupaal

Open Source Projects

Anatomy of Open Source Projects

A typical open source project has the following types of people →

Bigger projects can also have teams working on a particular task such as tooling, community moderation, etc. Documentation for the project has the following structure →

The following are used for an organized discussion →

Contribution Process Flow

To make a contribution to a repository, first fork the repository to a personal repository. Then follow the steps of forkclonepushPR. These steps basically mean →

Facts

The base repository from which a fork is made is also called the upstream repository. For git config, omit the --global argument to make config changes to only the current git repository.

C/C++ Projects

Project Structure

A basic structure for a C/C++ project would be as follows →

Project_name
|
|---- CMakeLists.txt
|
|---- include
| |
| |---- Project_name
| |
| |---- public_header(s).h
|
---- src
| |
| |---- private_header(s).h
| |
| |---- code(s).cpp
|
|---- libs
| |
| |---- A
| |
| |---- B
|
|---- tests

This could be an example of a library which can be used directly or by a third party. The idea behind the structure is to keep private headers separate from public headers (for functions which anyone using the library can invoke).

Structure Explanation

Build System Generation - CMake

CMake is not a build system but a tool that is a build system generator. Usually, a C++ project requires just the following steps after a GitHub clone to run →

mkdir build
cd build
cmake
..
make

The first step is just to keep the code clean and build everything in a separate directory. The third step i.e., the cmake call is to generate a Makefile. An example is, say one has the following files - main.cpp, a.cpp, a.h, b.cpp, b.h. The main.cpp file has the main() function and depends on the files a.cpp and b.cpp. Therefore, one must run g++ compiler on a.cpp, then on b.cpp and finally on main.cpp with the compile flag i.e., -c.

[!INFO] Header files are not compiled. They just tell the compiler about the function declaration.

Then to link them all together, one must call → g++ a.o b.o main.o -o binary. Thus, one must compile and link the code in two different orderly steps to generate a binary/executable. To make this easy for a number of files, a tool or a build system named Make was developed. This reduced the steps to writing a Makefile and then running the make tool by pointing it to the location of the Makefile.

Though after a point, writing Makefiles too became tedious, the solution to which was a tool called cmake which generates Makefiles using a relatively simpler and easy to maintain CMakeLists.txt file. This is why it’s called a build system generator. Thus, running the cmake tool and pointing it to the location of the CMakeLists.txt file creates a Makefile in the present working directory. After this, just calling the make tool to work on the Makefile will build the project (by default it looks under the build directory unless a specific location is specified).

CMakeLists details and file types

The main types of files that are dealt with are → archive files (.a), shared objects (.so), header files (.h) and objects (.o). To create object files, the .c or .cpp file is compiled with the -c flag. Files with suffix .a are called archived files which are used as a statically linked binary i.e., when it is linked with the program, all code defined in the archive is included in the binary.

To create the archive, the following command is used → 

gcc -c source1.c
gcc -c source2.c
ar -cvq libtest.a source1.o source2.o
gcc main.c libtest.a -o final

The last command is where the main.c file is compiled with the archive file. The files with the suffix .so are shared objects which are not included in the final executable. To create a shared library, an object file must be compiled as position independent code or PIC. This is done as follows → 

gcc -fPIC -c source1.c source2.c gcc -shared -o libtest.so source1.o source2.o

To link the code of either archived files or shared objects, the -l flag is used as follows →

gcc main.c -ltest -o final

The -ltest argument looks for either libtest.so or libtest.a. Some projects provide both a dynamic shared library and a statically linked library to let the user choose the required method. The other way for using a statically linked library is as shown earlier while creating the archive. Header files (.h) are not compiled. They are included in .c files to inform the compiler about functions. Once the compiler cross-references everything, their information is discarded and they do not affect the final executable size. The capital i flag, -I, is used to specify which directories contain header files. The flag -L is used to tell the compiler which directories contain the static libraries (.a). The lowercase l flag, -l, is used to specify the name of the library to be linked i.e., -l test means that the compiler looks for libtest.a or testlib.a or a similar shared object.

CMake

A standard compile command needs to have the following basic things →

This is automated by CMake, which has 5 major sections - flags, files, include, targets, external libraries, unit testing. In order, a CMakeLists.txt file will have the following (prefixed with #) →

This concludes the CMakeLists.txt file. An example CMakeLists.txt file is as follows →

cmake_minimum_required( VERSION 3.0 )
project( sample_cmake )

# flags
# include files
include_directories( ./include ./src ./libs/Logger/include ./libs/Randomize/include )

# target
add_executable( binary ./src/main.cpp ./src/game_engine.cpp ./src/game_interface.cpp )

# 3rd party libs
add_subdirectory( ./libs/Logger )
target_link_libraries( binary logger )

add_library(randomize STATIC IMPORTED)
set_property(TARGET randomize PROPERTY IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/libs/Randomize/librandomize.a)
target_link_libraries( binary randomize )