MRC/Tutorials/Setting up your project

From Control Systems Technology Group
Revision as of 20:35, 23 February 2021 by S134668 (talk | contribs) (→‎A note on CMake)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Of course, we not only want to use software during this course, but we want to create some! Let's create a workspace directory in which we will put this code:

mkdir ~/emc

Let's start off with a simple example project. Go inside the ~/emc directory, and create a new folder with the name my_project:

cd ~/emc
mkdir my_project

Often, the code files are not put directly in the root of a folder, but in a directory called src. This stands for source, and is called this way because the files in there are the source of the compilation process, and are converted into binaries (files that are no longer human-readable, but are understandable for the machine). So, let's go. Remember that when using cd (and many other commands in linux) you can use tab-completion to type quicker, i.e, try:

cd m<<< now push the TAB key >>>

You will see that the terminal fills out the rest, because my_project is the only directory in the current directory that starts with an m. Ok, create the src directory, and go inside:

mkdir src
cd src

Finally, let's do some programming! You should have finished the C++ tutorials by now, so you know how to create a basic C++ program. Let us do it now. Open your favourite editor to create a file called example.cpp (e.g. gedit example.cpp) and put some code inside:

#include <iostream>

int main()
{
    std::cout << "Hello world!" << std::endl;
    return 0;
}

Remember that you can compile the project using g++:

g++ -o example example.cpp

This will generate a file called example that you can run. Now, actually, our nice src is already not as clean as it should be. It should contain only source files, not binaries! No worries, let's go one directory down:

cd ..

Create a bin folder for our libraries:

mkdir bin

And run compilation as follows:

g++ -o bin/example src/example.cpp

Now our binary is create in the bin directory, while the source is in src: nicely seperated! You can run the program using:

bin/example

By the way, just remove the example binary we created 'wrongly' in the src directory using:

 rm src/example

And we are good to go.

Using the EMC framework

So, we've got a C++ source file that we can compile, but it is still not very useful. We have to build software that runs on a robot and performs the complex task of solving a maze. Starting from scratch would take a lot of time, but fortunately a lot is already provided! Actually it was already secretly sitting on your computer, being installed by the install script. This software that is provided is not something that is runnable on its own, but a set of functions and C++ classes that we can use in our own project. Such a set of reusable software parts is called a software library. Now, we have to include this installed library in the project.

Open the example.cpp file, and change it to the following:

#include <emc/io.h>

#include <emc/io.h>
#include <unistd.h>
 
int main()
{
   emc::IO io; 
   while( io.ok() )
   {
      sleep(1);
      io.speak("test " );
   }
      
   return 0;
}

The include statement on top includes the emc framework in your source file, which means that all functions, classes, etc declared there can be used by your project. The io object is something we will use to build our application with. Don't worry about it now, we'll get back to that later.

Try to compile the project (make sure to 'be' in your project root, i.e.: ~/emc/my_project):

g++ -o bin/example src/example.cpp

Woah, errors! Note that the error states something about undefined reference. We included emc/engine.h so we should be fine right? No: often *.h-files only declare functions etc, but they do not define them, that is: they tell the compiler something with that name is out there, but they do not provide the actual implementation. We need to tell the compiler where the implementation, which is already compiled into binary form, is. This is called linking, and we need to specify it in the g++ command:

g++ -o bin/example src/example.cpp -lemc-framework

Here, the -l specifies that g++ should link the program to the library that is called emc-framework. For those who are wondering, the compiler does not grab emc-framework out of thin air. Take a look in the /usr/lib directory: you will find libemc-framework.so sitting there, along with many other libraries. The extension so stands for shared object: it is a piece of software that can be shared across different applications. The h-files, which are called header files can be found in /usr/include (e.g., look for /usr/include/emc/engine.h). To run this example, you will first need to start a ROS master by opening a new terminal and executing the "roscore" command, which will be explained further in part 8

CMake

So, we compiled a source file into a binary in another directory, while linking against the emc-framework library. Imagine that by the end of the course you will use more libraries, and every time you need to remember the g++ command. That's quite a nuisance! Fortunately, there are tools that will help you out. In this course, we will use CMake.

A very short history: when Linux programmers started to become annoyed with typing the g++ (or rather gcc back in those days, the compiler for plain C), they invented Make, a "tool which controls the generation of executables and other non-source files of a program from the program's source files". Make allows you to specify compiler options, linking, etc of your project in a file, and once that was done, you only had to run make to compile your project. However, other Operating Systems created their own build systems (that is what these tools are called), e.g., Microsoft uses Visual Studio for Windows. Then some people came up with CMake which is a cross-platform (that is what the C stand for) build tool: it can be used on Linux, Mac OS and Windows. In fact, it builds on top of the OS-specific build tool. For example, on Linux, CMake generates Make-files, while on Windows it generates files that can be used by Visual Studio. That's quite useful! It allows programmers from all over the world to collaborate on software projects, even if they are using different Operating Systems!

Enough talking, let's start using CMake to build our project, such that compilation becomes easier. All you have to do is create a file in the root of you project that can be read by CMake, called CMakeLists.txt. In it, you specify the instructions that are needed to compile the project. Create a file called CMakeLists.txt (e.g. gedit CMakeLists.txt) with this text:

cmake_minimum_required(VERSION 2.8)
project(my_project)

add_executable(example src/example.cpp)

This file is probably quite understandable at first sight: it specifies the minimum required version of CMake to read the file, the name of your project, and states that an executable called example should be created from the source file src/example.cpp.

Now, how should we use this thing? As was already said, CMake does not directly call the compiler. Instead, it generates Makefiles which can be used by the Linux-dependent Make tool. These Makefiles, or more generically called build files, are created in a seperate folder, often called build. Go to the root of your project (cd ~/emc/my_project), create a build directory and go inside:

mkdir build
cd build

Now, to generate the build files, we only have to call CMake and refer to the directory in which the CMakeLists file is we just created:

cmake ..

(remember .. stands for 'one directory up')

Have a look inside the build directory: CMake generated a lot of files, one of which is a Makefile. Now while 'being' in the build directory, call Make:

make

You will see that the compiler is called (as if we started g++ ourselves). Oh whoops... Again the undefined reference error. But this makes sense: we did not specify yet that we need to use the emc-framework library. Doing so in CMake is easy. Edit the CMakeLists.txt file, and add below the add_executable statement:

target_link_libraries(example emc-framework)

Now run cmake and make again:

cd build
cmake ..
make

Success! However, the binary is also created in the directory where you called make, i.e. in the build directory. It would be nice to have it in the bin directory we created earlier. Well, we can. Just add the following line to your CMakeList.txt:

set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

This sets the CMake variable EXECUTABLE_OUTPUT_PATH to be the bin directory in your project directory. The ${...} simply returns the value of the variable inside. The CMake variable PROJECT_SOURCE_DIR is set by CMake and refers to the directory in which your CMakeLists.txt is placed (the name is somewhat confusing...). Try to run cmake .. and make again. You should see the executable appear in your bin directory.

By the way, you only need to run the cmake command if you change your CMakeLists.txt. Once you start programming, i.e, editing your source files, you can simply go to the build directory and run make.

A note on CMake

You might be thinking: this still is a lot of hassle. The g++ command wasn't that bad, and now I need an extra directory, some extra commands, understand CMake, etc. Indeed, for the small example above CMake is a bit overkill. However, once your project grows and source files are added, more libraries are used, etc, you will see that it is quite handy to have your project set-up defined in one simple file. Furthermore, you won't have to tell your teammates what command to run if you add a file or library, as you can simply update the CMakeLists.txt. Also, CMake is a widely used language, and supported by many Integrated Development Environments (IDE), which can be thought of as really smart programming editors that not only provide editing, but also support compiling and even runnning and debugging your program. Having a CMakeLists.txt is having a project definition that can be used by many IDEs to 'understand' what is going on in your project.