DEV Community

Cover image for Cross-platform C/C++ Development with Conan
Artem
Artem

Posted on • Updated on

Cross-platform C/C++ Development with Conan

Conan is an open source and multi-platform package manager to create and share native binaries (mostly C and C++). It is written in Python and supports Windows, Linux, and Mac.

Besides managing dependencies, conan simplifies development process by integrating with various build tools. I touched on its most basic usage in tandem with cmake in my previous blog-post titled "Building HTTP Service in C++". However, that post aimed primary towards Mac and Linux users, and developers using Windows platform were left aside.

Today, let's see how we can use conan to manage cross-platform development.

Let's start by creating a conanfile. Instead of using plaintext file, like in the HTTP service post, we use a Python script. Conan has ability to work with both types.

conanfile.py

from conans import ConanFile, CMake, tools

class MyApp(ConanFile):
    name = "my_app"
    version = "1.0"
    license = "unlicensed"
    author = "Artem Akatev"
    settings = "os", "compiler", "build_type", "arch"
    options = {"shared": [True, False], "fPIC": [True, False]}
    default_options = {"shared": False, "fPIC": True}
    generators = "cmake"
    requires = "boost/1.75.0"

    def config_options(self):
        if self.settings.os == "Windows":
            del self.options.fPIC

    def build(self):
        cmake = CMake(self)
        cmake.configure(source_folder="src")
        cmake.build()
Enter fullscreen mode Exit fullscreen mode

Two important directives in the file are generators and requires. The first one contains a list of build tools, and the second a list of all dependencies. Both lists are comma delimited.

The build section (Python function) contains cmake configuration. The cmake is set to expect CMakeLists.txt file in src folder, so let's create the file.

src/CMakeLists.txt

cmake_minimum_required(VERSION 2.8.12)
project(my_app)

add_definitions("-std=c++11")

include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()

add_executable(my_app main.cc)
target_link_libraries(my_app ${CONAN_LIBS})
Enter fullscreen mode Exit fullscreen mode

Now we need some application to compile. I will "steal" an example of HTTP client from Boost Beast.


#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <cstdlib>
#include <iostream>
#include <string>

namespace beast = boost::beast;     // from <boost/beast.hpp>
namespace http = beast::http;       // from <boost/beast/http.hpp>
namespace net = boost::asio;        // from <boost/asio.hpp>
using tcp = net::ip::tcp;           // from <boost/asio/ip/tcp.hpp>

// Performs an HTTP GET and prints the response
int main(int argc, char** argv)
{
    try
    {
        // Check command line arguments.
        if(argc != 4 && argc != 5)
        {
            std::cerr <<
                "Usage: http-client-sync <host> <port> <target> [<HTTP version: 1.0 or 1.1(default)>]\n" <<
                "Example:\n" <<
                "    http-client-sync www.example.com 80 /\n" <<
                "    http-client-sync www.example.com 80 / 1.0\n";
            return EXIT_FAILURE;
        }
        auto const host = argv[1];
        auto const port = argv[2];
        auto const target = argv[3];
        int version = argc == 5 && !std::strcmp("1.0", argv[4]) ? 10 : 11;

        // The io_context is required for all I/O
        net::io_context ioc;

        // These objects perform our I/O
        tcp::resolver resolver(ioc);
        beast::tcp_stream stream(ioc);

        // Look up the domain name
        auto const results = resolver.resolve(host, port);

        // Make the connection on the IP address we get from a lookup
        stream.connect(results);

        // Set up an HTTP GET request message
        http::request<http::string_body> req{http::verb::get, target, version};
        req.set(http::field::host, host);
        req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);

        // Send the HTTP request to the remote host
        http::write(stream, req);

        // This buffer is used for reading and must be persisted
        beast::flat_buffer buffer;

        // Declare a container to hold the response
        http::response<http::dynamic_body> res;

        // Receive the HTTP response
        http::read(stream, buffer, res);

        // Write the message to standard out
        std::cout << res << std::endl;

        // Gracefully close the socket
        beast::error_code ec;
        stream.socket().shutdown(tcp::socket::shutdown_both, ec);

        // not_connected happens sometimes
        // so don't bother reporting it.
        //
        if(ec && ec != beast::errc::not_connected)
            throw beast::system_error{ec};

        // If we get here then the connection is closed gracefully
    }
    catch(std::exception const& e)
    {
        std::cerr << "Error: " << e.what() << std::endl;
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

Enter fullscreen mode Exit fullscreen mode

Next, open a terminal window in the project's root directory (same as the directory containing conanfile.py), and follow these steps to build and run the application:

1. Install build dependencies

conan install . --install-folder build
Enter fullscreen mode Exit fullscreen mode

2. Build binary

conan build . --build-folder build
Enter fullscreen mode Exit fullscreen mode

Run your application

Mac and Linux

./build/bin/my_app google.com 443
Enter fullscreen mode Exit fullscreen mode

Windows

build\bin\my_app.exe google.com 443
Enter fullscreen mode Exit fullscreen mode

Discussion (1)

Collapse
grizzlysmit profile image
Francis Grizzly Smit

interesting easily the simplest build tool I've seen, you say it's a package manager as well I love to see how that works or did I miss something