Using Docker to build and run a project in C ++

This post discusses how to build a C ++ project using GTest and Boost using Docker. The article is a recipe with some explanatory comments. The solution presented in the article does not claim to be Production-ready.


Why and who might need it?


Suppose that, like me, I really like the concept of Python venv , when all the necessary dependencies are located in a separate, strictly defined directory; or you need to ensure simple portability of the build and testing environment for the project being developed, which is very convenient, for example, when you join a new developer to the team.


This article will be especially useful for novice developers who need to perform basic environment settings for building and running a C ++ project.


The environment presented in the article can be used as a framework for test tasks or laboratory work.


Docker installation


All you need for the project presented in this article is Docker and Internet access.


Docker is available for Windows, Linux and Mac platforms. Official documentation .


Since I use a Windows machine on board, it was enough for me to download the installer and run it.


It should be noted that at the moment Docker for Windows uses Hyper-V for its work.


Project


As a project, we mean the CommandLine application, which displays the string "Hello World!" to standard output stream.


The project will use the required minimum of libraries, as well as CMake as an assembly system.


The structure of our project will be as follows:


project | Dockerfile | \---src CMakeLists.txt main.cpp sample_hello_world.h test.cpp 

The CMakeLists.txt file contains a description of the project.
File source code:


 cmake_minimum_required(VERSION 3.2) project(HelloWorldProject) #  C++17 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17") #  Boost.Program_options #   ,     set(Boost_USE_STATIC_LIBS ON) find_package(Boost COMPONENTS program_options REQUIRED) include_directories(${BOOST_INCLUDE_DIRS}) #     add_executable(hello_world_app main.cpp sample_hello_world.h) target_link_libraries(hello_world_app ${Boost_LIBRARIES}) #  CTest enable_testing() #       GoogleTest find_package(GTest REQUIRED) include_directories(${GTEST_INCLUDE_DIRS}) #    add_executable(hello_world_test test.cpp sample_hello_world.h) target_link_libraries(hello_world_test ${GTEST_LIBRARIES} pthread) #       CTest add_test(NAME HelloWorldTest COMMAND hello_world_test) 

The file sample_hello_world.h contains the HelloWorld class description, sending an instance of which to the stream will display the string "Hello World!". This complexity is due to the need to test the code of our application.
File source code:


 #ifndef SAMPLE_HELLO_WORLD_H #define SAMPLE_HELLO_WORLD_H namespace sample { struct HelloWorld { template<class OS> friend OS& operator<<(OS& os, const HelloWorld&) { os << "Hello World!"; return os; } }; } // sample #endif // SAMPLE_HELLO_WORLD_H 

The main.cpp file contains the entry point of our application, we also add Boost.Program_options to simulate a real project.


File source code:


 #include "sample_hello_world.h" #include <boost/program_options.hpp> #include <iostream> //         - "--help" auto parseArgs(int argc, char* argv[]) { namespace po = boost::program_options; po::options_description desc("Allowed options"); desc.add_options() ("help,h", "Produce this message"); auto parsed = po::command_line_parser(argc, argv) .options(desc) .allow_unregistered() .run(); po::variables_map vm; po::store(parsed, vm); po::notify(vm); //  C++17     std::make_pair return std::pair(vm, desc); } int main(int argc, char* argv[]) try { auto [vm, desc] = parseArgs(argc, argv); if (vm.count("help")) { std::cout << desc << std::endl; return 0; } std::cout << sample::HelloWorld{} << std::endl; return 0; } catch (std::exception& e) { std::cerr << "Unhandled exception: " << e.what() << std::endl; return -1; } 

The test.cpp file contains the required minimum — a test of the HelloWorld class functionality. For testing, use GoogleTest .
File source code:


 #include "sample_hello_world.h" #include <sstream> #include <gtest/gtest.h> //  ,  HelloWorld  ,     TEST(HelloWorld, Test) { std::ostringstream oss; oss << sample::HelloWorld{}; ASSERT_EQ("Hello World!", oss.str()); } //      int main(int argc, char **argv) { testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } 

Next, let's move on to the most interesting - setting up the build environment using Dockerfile!


Dockerfile


For the assembly we will use the latest version of gcc .
Dockerfile contains two stages: building and running our application.
To start using the latest version of Ubuntu.


Contents of Dockerfile:


 #  --------------------------------------- #        gcc:latest FROM gcc:latest as build #      GoogleTest WORKDIR /gtest_build #        GoogleTest #     ,  # Docker   RUN   , #   ,   ,   RUN apt-get update && \ apt-get install -y \ libboost-dev libboost-program-options-dev \ libgtest-dev \ cmake \ && \ cmake -DCMAKE_BUILD_TYPE=Release /usr/src/gtest && \ cmake --build . && \ mv lib*.a /usr/lib #   /src   ADD ./src /app/src #       WORKDIR /app/build #    ,     RUN cmake ../src && \ cmake --build . && \ CTEST_OUTPUT_ON_FAILURE=TRUE cmake --build . --target test #  --------------------------------------- #      ubuntu:latest FROM ubuntu:latest #  ,    Docker    root #     root'  :) RUN groupadd -r sample && useradd -r -g sample sample USER sample #      WORKDIR /app #         COPY --from=build /app/build/hello_world_app . #    ENTRYPOINT ["./hello_world_app"] 

I suppose, for now, proceed to build and run the application!


Build and Run


To build our application and create a Docker image, it is enough to run the following command:


 #  docker-cpp-sample    # . -    ,  Dockerfile docker build -t docker-cpp-sample . 

To run the application, use the command:


 > docker run docker-cpp-sample 

We will see the cherished words:


 Hello World! 

To pass a parameter, it is enough to add it to the above command:


 > docker run docker-cpp-sample --help Allowed options: -h [ --help ] Produce this message 

Summing up


As a result, we created a full-fledged C ++ application, set up the environment for building and running it under Linux, and wrapped it in a Docker container. Thus, freeing subsequent developers from having to spend time setting up a local assembly.

Source: https://habr.com/ru/post/414109/


All Articles