Monday, April 22, 2013

Boost.Build (Part 2)

For this post, I want to do something a little more practical. Anyone can sift through the boost examples on the website and post a rehash. I want to produce some original content because 1) rehashes aren't useful and 2) the boost examples sometimes are not appropriate.

The Goal: Put together a stub to build a Client/Server application where the Client/Server both use a library called Messages to implement a protocol. This post will not go into threading or asio. I only want to illustrate how to put together a project with multiple executables that relies on a single library.

Project Organization: I'm putting the client/server/messages into their own directories:

app
  client
    main.cpp
  server
    main.cpp
  messages
    messages.h
    messages.cpp

I'll create a container object in messages that contains all message objects in the system. That way I can grab the container from client or server and print the contents. This proves that the library gets linked:

File: messages.h
#include <boost/noncopyable.hpp>
#include <string>
#include <vector>

class Message : public boost::noncopyable
{
     protected:
         explicit Message(std::string const& id);

     public:
         virtual ~Message();
         std::string const& getId() const;

     private:
         std::string id;
};

class Messages : public boost::noncopyable
{
    public:
        typedef std::vector<Message*> MessagesContainer;
        typedef MessagesContainer::iterator Iterator;
        
        Messages();
        ~Messages();

        Iterator begin();
        Iterator end();

    private:
        MessagesContainer messages;        
};

File: messages.cpp

#include "messages.h"

Message::Message(std::string const& idIn) : id(idIn) { }
Message::~Message() { }
std::string const& Message::getId() const
{
    return this->id;
}

class Message1 : public Message 
{
    public:
        Message1() : Message("message1") { }
};

Messages::Messages() 
: messages() 

    this->messages.push_back(new Message1()); 
}

Messages::~Messages() { /* Add messages deletion */ }
Messages::Iterator Messages::begin() { return this->messages.begin(); }
Messages::Iterator Messages::end() { return this->messages.end(); }

File: main.cpp

/**
 * The main is just a simple test program that 
 * prints out the contents of the 
 * messages. We don't really do anything here.
 */
#include "messages/messages.h"
#include <algorithm>
#include <iostream>

void print(Message* const& message)
{
    std::cout << message->getId() << std::endl;
}

int main(int , char** )
{
    Messages m;
    std::for_each( m.begin(), m.end(), print );
    return 0;
}


Now comes the point of this post: where do we put the Jamfiles and Jamroot files? The first experiment I did was to simply build the messages library using Boost.Build:

File: messages/Jamroot
lib messages : messages.cpp : <include>.. ;

If you don't put the semicolon at the end, you'll get an error stating:
Jamroot:1: syntax error at EOF

Building the library is a great first step- but we need to build this as part of a larger project. Let's place a Jamroot at the root of the project that will in turn build the messages library:

File: Jamroot
build-project messages ;

NOTE: Make sure to put a space between messages and the semicolon! Boost.Build is unnecessarily picky about this for some reason! This call will of course build the project messages (which has its own Jamroot file!). Let's flesh the project out a bit more with the file to build the client and server stubs:

File: client/Jamroot
exe client : main.cpp ../messages//messages : <include>.. ;

File: server/Jamroot
exe server : main.cpp ../messages//messages : <include>.. ;

File: Jamroot (EDIT)

build-project messages ;
build-project client ;
build-project server ;


NOTE: Make sure there are no spaces between <include> and the ..- If there are spaces there will be vague errors! The last file modified in the root Jamroot since we want to build client and server. Notice that in the client/server Jamroot's, we have additional dependencies that we want to build (../messages//messages).

After building, this is what the directory structure will look like:


app/
  client/
    main.cpp
    Jamroot
    bin/
      gcc-4.7/
        debug/
          client (bin)
  server
    main.cpp
    Jamroot
    bin/
      gcc-4.7/
        debug/
          server (bin)
  messages
    messages.h
    messages.cpp


    Jamroot
    bin/
      gcc-4.7/
        debug/
          libmessages.so (bin)


In conclusion I have to say that the parser for Boost.Build needs a lot to be desired! Positioning of whitespace shouldn't matter but it does with the Jamroot files! The documentation also perplexes me: when do I name a file a Jamfile vs. Jamroot? Also, what's the deal with using xml-like tags inside of Makefile looking syntax? Consistency is key when attracting new users. I can honestly say with confidence that I won't recommend using this tool in the future. Instead I'll fall back to premake4, qmake, or cmake as they are much easier and consistent to use.

(I would normally begin with what I found appealing; I couldn't find anything. Sorry Boost.Build!)




No comments:

Post a Comment