Project Organization
In this part of the tutorial we’ll develop a project SophisticatedMath. We will do it in a three-person team. Each member of the team will use a different type of IDE, Visual Studio, Vs Code, QT Creator respectably. We’ll be utilize IDE only for writing code. We’ll compile source code outside of the IDE manually. Our task will be to create two executable and two libraries (static and dynamic ones). We will consider how to organize the work of our team, so that local settings of one person’s programming environment do not have a destructive impact on the work of other team members. The structure of our projects should be easy to read and follow the same pattern.
GitHub Repository
We are Cmaketopians thus we leverage a GitHub version control repository to backup and share our ideas. Our repositories should have the appropriate structure of directories and files based on convention. The similar structure help us reading easily other projects. In general our repository contains source code, data and documentation of projects, as well as LICENSE, README.md and .gitignore files.
GitHub Repository
|
| .gitignore
| LICENSE
| README.md
|
+---Docs
| Settings.md
|
\---src
+---Level04
| +---CheckPredefines
| | checkpredefines.cpp
| | checkpredefines.h
| |
| +---Hello
| | hello.cpp
| | hellowitherror.cpp
| |
| +---SQLite
| | sqlite-amalgamation-3270000.zip
| |
| \---Sum
| Calculator.cpp
| Calculator.h
| Sum.cpp
|
+---Level05
\---SophisticatedMath
|
+---AreaCalculation
| AreaCalculator.cpp
|
+---Calculator
| Calculator.cpp
| Calculator.h
| shared_EXPORTS.h
|
+---Docs
| readme.txt
|
+---PerimeterCalculation
| PerimeterCalculator.cpp
|
\---Tests
test.cpp
The GitHub repository is organized as follows:
- .gitignore file - specifies intentionally untracked files to ignore
- LICENSE file - help other persons to understand an official permission you give to them
- README.md file - contains general information about a repository
- Docs subfolder - includes additional files of documentation
- src subfolder - embraces source code
For the sake of this tutorial src directory is divided into suites/kits/bundles. Each suite represents topics/projects created in a particular part of the tutorial. We should create README.md file in your repository to explain other people what is a goal of our project, why it’s useful and how they can use it. This is where you should place basic documentation of your repository. When we share open source software we ought to license it. A software license will tells others what they can and can’t do with our source code. Our local repository may contain configuration files, build output, or just backup files or user specific files created by IDE. To avoid pushing these files to GitHub repository we need specify unique file, types of files or directories in our .gitignore file.
See an example below.
# VS Code
.vscode/
build/
# Visual Studio
.vs/
CMakeSettings.json
# QT Creator
build*/
CMakeLists.txt.user
# Ignore files
*.exe
*.dll
*.so
*.obj
*.o
*.lib
*.a
For example this .gitignore blocks .vscode, .vs, build folders and binary files to be copied to the GitHub repository.
C++ Project Structure
We are beginners thus the full GNU C++ standard template is overkill for our simple projects. We try to structure the project into a form as simple as possible. Because we create cross-platform projects our domain project repository should be platform independent. Additional platform specific modules should create platform specific projects. We split source files into subdirectories to form logical file tree. The most natural way of organizing C++ project is spreading source code into smaller parts. The modularity, code reuse and separation of concerns should be taken into account while dividing the code into logical parts. Common tasks can be grouped into functions and classes. They based on code reuse can be joined into file. Files predicated on separation of concerns can create a library. Libraries should be spread across different subdirectories in the source tree. Each executable should be placed in a different subdirectory also. This practice helps organizing code within a project. Because each library and executable can be build separately it simplifies and speeds up recompilation of a project during development. The source tree creates a hierarchy thus we call this source organization hierarchical project. We have a master directory and a subdirectory for each library and executable. A master directory name of our project is SophisticatedMath. Each logical part of source code should be located in its own subdirectory. Our project has five parts, two parts for applications, one for libraries (static or dynamic), one for documentation and last for tests. AreaCalculation application counts area of figures and depends on static library created from source code located in Calculator folder. On the other hand PerimeterCalculation app counts a perimeter of figure and depends this time on a dynamic library from the same source code like the static library (a common code located Calculator in subdirectory. To achieve this we use a solution bases on defining symbols). Docs folder contains information about projects. Tests folder includes tests. The products of building will be placed in build folder. Inside build folder there are two folders bin and lib subfolders. Bin folder is for executables and dynamic libraries. Lib folder is intended for static, import libraries and export files.
The directory structure of a project
\---SophisticatedMath
+---AreaCalculation
| AreaCalculator.cpp
+---build
| \
| |
| +--bin
| | *.exe; *.dll
| |
| \--lib
| *.a; ^.lib; *.exp
|
+---Calculator
| Calculator.cpp
| Calculator.h
| shared_EXPORTS.h
|
+---Docs
| readme.txt
|
+---PerimeterCalculation
| PerimeterCalculator.cpp
|
\---Tests
test.cpp
Remark ! Build subdirectory is not placed on GitHub. This is a local folder utilized to store project binaries on a developer’s machine.
Because we are funs of DRY we’ll be building static and dynamic library from the same source code. To do that we’ll use of preprocessor definition and this piece of code.
#ifndef _SHARED_EXPORTS_H__
#define _SHARED_EXPORTS_H__
#if defined(_WIN32) && defined(DLL_BUILD)
#ifdef shared_EXPORTS
#define SHARED_EXPORT __declspec(dllexport)
#else
#define SHARED_EXPORT __declspec(dllimport)
#endif
#else
#define SHARED_EXPORT
#endif
#endif /* _SHARED_EXPORTS_H__ */
How does it work? If it’s Windows platform and we defined DLL_BUILD symbol, the value of SHARE_EXPORT depends on shared_EXPORTS symbol. If shared_EXPORTS is defined SHARED_EXPORT equals __declspec(dllexport). If not, SHARED_EXPORT equals __declspec(dllimport). Otherwise SHARED_EXPORT is empty. This trick allows us to define exports for a linker. Then we use SHARED_EXPORT symbol in Calculator.h file.
For the purposes of testing we create a very simple test application with no unit test framework. Then the app is placed in Tests subfolder.
#include <assert.h>
#include "Calculator.h"
#undef NDEBUG /* Force assert output if any */
void main()
{
Calculator calc;
assert(calc.Add(2.0,2.0)==4.0);
assert(calc.Add(2.0,3.0)==4.0); /* Error in the test case to show output of assertion*/
}
Now time for a party. We’ll be building 64-bit apps so open a correct compiler environment. Irrespective of a used compiler we can describe a process of building AreaCalculator very simple:
- make directory build if not exists
- switch to build directory
- create bin subfolder if not exists
- create lib subfolder if not exists
- create Calculator object file
- create static library from Calculator object file and put it in lib folder
- create AreaCalculation object file
- link all together to produce areacalc.exe executable and place it in bin folder
and similar steps to create PerimeterCalculator app:
- make directory build if not exists
- switch to build directory
- create bin subfolder if not exists
- create lib subfolder if not exists
- create Calculator object file
- create dynamic library from Calculator object file and put it in lib folder
- create PerimeterCalculator object file
- link all together to produce perimcalc.exe executable and place it in bin folder
Let’s do it!
First we create build folder structure inside our master folder.
- mkdir build
- cd build
- mkdir bin
- mkdir lib
Now we are in “build” subfolder, so we begin to compile our solution.
Building AreaCalculator app (areacalc.exe)
To compile objects without errors we must inform the compiler in what folder it can find header files. For this we use -I option.
GNU toolchain
Create | GCC |
---|---|
Calculator object file | g++.exe -I../Calculator1 -c ../Calculator/Calculator.cpp |
Static library | ar.exe qc lib/libcalc-static.a Calculator.o |
Main object file | g++.exe -I../Calculator -c ../AreaCalculation/AreaCalculator.cpp |
Executable | g++.exe AreaCalculator.o -o bin/areacalc.exe lib/libcalc-static.a |
Microsoft toolchain
Create | MSVC |
---|---|
Calculator object file | cl.exe /nologo -I..\Calculator. /EHsc /FoCalculator.obj -c ..\Calculator\Calculator.cpp |
Static library | link.exe /lib2 /nologo /machine:x64 /out:lib\calc-static.lib Calculator.obj |
Main object file | cl.exe /nologo -I..\Calculator. /EHsc /FoAreaCalculator.obj -c ..\AreaCalculation\AreaCalculator.cpp |
Executable | link.exe /nologo AreaCalculator.obj /out:bin\areacalc.exe /machine:x64 /subsystem:console lib\calc-static.lib |
Clang-cl toolchain
Create | MSVC |
---|---|
Calculator object file | clang-cl.exe /nologo -I..\Calculator. /EHsc /FoCalculator.obj -c ..\Calculator\Calculator.cpp |
Static library | lld-link.exe /lib2 /nologo /machine:x64 /out:lib\calc-static.lib Calculator.obj |
Main object file | clang-cl.exe /nologo -I..\Calculator. /EHsc /FoAreaCalculator.obj -c ..\AreaCalculation\AreaCalculator.cpp |
Executable | lld-link.exe /nologo AreaCalculator.obj /out:bin\areacalc.exe /machine:x64 /subsystem:console lib\calc-static.lib |
Building PerimeterCalculator app (perimcalc.exe)
GNU toolset
Create | GCC |
---|---|
Calculator object file | g++.exe -DDLL_BUILD -Dshared_EXPORTS3 -I../Calculator -fvisibility=hidden4 -o Calculator.o -c ../Calculator/Calculator.cpp |
Dynamic library | g++.exe -shared -o bin/libcalc-dll.dll -Wl,–out-implib,lib/libcalc-dll.dll.a Calculator.o |
Main object file | g++.exe -DDLL_BUILD -I../Calculator -o PerimeterCalculator.o -c ../PerimeterCalculation/PerimeterCalculator.cpp |
Executable | g++.exe PerimeterCalculator.o -o bin/perimcalc.exe lib/libcalc-dll.dll.a |
Microsoft toolset
Create | MSVC |
---|---|
Calculator object file | cl.exe /nologo -DDLL_BUILD -Dshared_EXPORTS -I..\Calculator /EHsc /FoCalculator.obj -c ..\Calculator\Calculator.cpp |
Dynamic library | link.exe /nologo Calculator.obj /out:bin\calc-dll.dll /implib:lib\calc-dll.lib /dll /machine:x64 |
Main object file | cl.exe /nologo -DDLL_BUILD -I..\Calculator /EHsc /FoPerimeterCalculator.obj -c ..\PerimeterCalculation\PerimeterCalculator.cpp |
Executable | link.exe /nologo PerimeterCalculator.obj /out:bin\perimcalc.exe /machine:x64 /subsystem:console lib\calc-dll.lib |
Clang-cl toolset
Create | MSVC |
---|---|
Calculator object file | clang-cl.exe /nologo -DDLL_BUILD -Dshared_EXPORTS -I..\Calculator /EHsc /FoCalculator.obj -c ..\Calculator\Calculator.cpp |
Dynamic library | lld-link.exe /nologo Calculator.obj /out:bin\calc-dll.dll /implib:lib\calc-dll.lib /dll /machine:x64 |
Main object file | clang-cl.exe /nologo -DDLL_BUILD -I..\Calculator /EHsc /FoPerimeterCalculator.obj -c ..\PerimeterCalculation\PerimeterCalculator.cpp |
Executable | lld-link.exe /nologo PerimeterCalculator.obj /out:bin\perimcalc.exe /machine:x64 /subsystem:console lib\calc-dll.lib |
Building test app (test.exe)
GNU
Create | GCC |
---|---|
Test object file | g++.exe -I../Calculator -c ../Tests/test.cpp |
Executable | g++.exe test.o -o bin/test.exe lib/libcalc-static.a |
MSVC
Create | MSVC |
---|---|
Test object file | cl.exe /nologo -I..\Calculator. /EHsc /Fotest.obj -c ..\Tests\test.cpp |
Executable | link.exe /nologo test.obj /out:bin\test.exe /machine:x64 /subsystem:console lib\calc-static.lib |
Clang-cl
Create | MSVC |
---|---|
Test object file | clang-cl.exe /nologo -I..\Calculator. /EHsc /Fotest.obj -c ..\Tests\test.cpp |
Executable | lld-link.exe /nologo test.obj /out:bin\test.exe /machine:x64 /subsystem:console lib\calc-static.lib |
After finishing a building process your build folder should look similar like that.
build
| AreaCalculator.o
| AreaCalculator.obj
| Calculator.o
| Calculator.obj
| PerimeterCalculator.o
| PerimeterCalculator.obj
| test.o
| test.obj
|
+---bin
| areacalc.exe
| calc-dll.dll
| libcalc-dll.dll
| perimcalc.exe
| test.exe
|
\---lib
calc-dll.exp
calc-dll.lib
calc-static.lib
libcalc-dll.dll.a
libcalc-static.a
Look at lib subfolder, there is calc-dll.exp file. When LIB creates an import library, it also creates an export file. For more details see Working with Import Libraries and Export Files
Let’s knock off for a cuppa , I’m parched. But wait, we received a mail from our company. We need add a data layer to persist our calculations. Oh my, extra modules, compilations, librarian and linker, oh no.
Sounds like we’re up shit creek without a paddle, mate. Instead of running around like a headless chicken, let’s make a list of the things we need to have so that we can solve the problem. The most tedious and time-consuming part of our activity is a building process. So we require to do that in a more effective way. We crave for building automation tools. Let’s find out, Google is our friend. Googling…. Don’t worry fellow, I found something. A build system comes to our rescue . See next part of the tutorial- A build system.
-
Instruct a compiler to search a directory for include files. ↩
-
Another way of lib.exe /nologo /machine:x64 /out:lib\calc-static.lib Calculator.obj. ↩ ↩2
-
To correctly export symbols to the object file we have to define preprocessor symbols. ↩
-
Because g++ by default exports all symbols we must change this. When we set -fvisibility=hidden option, only symbols tagged by an __declspec(dllexport) attribute will be exported. ↩