A Basic SCons Project Example

By Thursday, September 18, 2014 0 , Permalink 2

SCons is an open source software construction tool – a next generation build tool.

I have previously written an introduction to SCons.

This post is a short example of a basic C++ project, that uses SCons.

The purpose of this basic example is to set a baseline. It demonstrates how to build a non-trivial C++ project with out-of-the-box SCons. Future posts in my SCons series will describe various extensions to SCons, so it is important to be able to compare to a simple baseline.

Basic Project Overview

In my introduction post I included a “Hello World” example. This is too trivial to be interesting.

The less-trivial project I want to introduce is a simple (stupid?) address book C++ program.

The initial version includes a Person class. A person represented by the class can have a name, an ID, an Email address, and a phone number. The phone number itself is represented by a PhoneNumber class, with a number and a type members.

The data structures and the associated logic are in an AddressBook module. A separate Writer module contains a small program that prompts the user for one person details, and prints back the person name. (hey, I warned that this is stupid…)

Project Source Code

The entire project is available on my GitHub scons-series repository.

For your convenience, here’s the code for the address book project initial version:

// Copyright 2014 The Ostrich
// AddressBook data structures
// Author: ItamarO

#ifndef ADDRESSBOOK_ADDRESSBOOK_H_
#define ADDRESSBOOK_ADDRESSBOOK_H_

#include <string>

class PhoneNumber {
public:
    enum PhoneType {
        UNSPECIFIED = 0,
        MOBILE,
        HOME,
        WORK
    };
    std::string number() const;
    void set_number(const std::string& number);
    PhoneType type() const;
    void set_type(PhoneType type);

private:
    std::string number_;
    PhoneType type_;
};

class Person {
public:
    std::string name() const;
    void set_name(const std::string& name);
    int id() const;
    void set_id(int id);
    std::string email() const;
    void set_email(const std::string& email);
    PhoneNumber phone;

private:
    std::string name_;
    int id_;
    std::string email_;
};

#endif  // ADDRESSBOOK_ADDRESSBOOK_H_
// Copyright 2014 The Ostrich
// AddressBook data structures logic
// Author: ItamarO

#include <string>

#include "AddressBook/addressbook.h"

std::string PhoneNumber::number() const {
    return number_;
}

void PhoneNumber::set_number(const std::string& number) {
    number_ = number;
}

PhoneNumber::PhoneType PhoneNumber::type() const {
    return type_;
}

void PhoneNumber::set_type(PhoneType type) {
    type_ = type;
}

std::string Person::name() const {
    return name_;
}

void Person::set_name(const std::string& name) {
    name_ = name;
}

std::string Person::email() const {
    return email_;
}

void Person::set_email(const std::string& email) {
    email_ = email;
}

int Person::id() const {
    return id_;
}

void Person::set_id(int id) {
    id_ = id;
}
// Copyright 2014 The Ostrich

// Author: ItamarO

#include <iostream>  // NOLINT(readability/streams)
#include <string>

#include "AddressBook/addressbook.h"

using std::cout;
using std::cin;
using std::getline;
using std::string;

// This function fills in a Person object based on user input.
void PromptForAddress(Person* person) {
    cout << "Enter person ID number: ";
    int id;
    cin >> id;
    person->set_id(id);
    cin.ignore(256, '\n');

    cout << "Enter name: ";
    string name;
    getline(cin, name);
    person->set_name(name);

    cout << "Enter email address (blank for none): ";
    string email;
    getline(cin, email);
    person->set_email(email);

    cout << "Enter a phone number (or leave blank to finish): ";
    string number;
    getline(cin, number);
    if (!number.empty()) {
        person->phone.set_number(number);
        cout << "Is this a mobile, home, or work phone? ";
        string type;
        getline(cin, type);
        if (type == "mobile") {
            person->phone.set_type(PhoneNumber::MOBILE);
        } else if (type == "home") {
            person->phone.set_type(PhoneNumber::HOME);
        } else if (type == "work") {
            person->phone.set_type(PhoneNumber::WORK);
        } else {
            cout << "Unknown phone type.  Using UNSPECIFIED.\n";
            person->phone.set_type(PhoneNumber::UNSPECIFIED);
        }
    }
}

// Main function:  Reads the entire address book from a file,
//   adds one person based on user input, then writes it back out to the same
//   file.
int main(int argc, char* argv[]) {
    Person person;

    // Get an address.
    PromptForAddress(&person);

    cout << "Got: " << person.name() << "\n";

    return 0;
}

Building the Project with SCons

Here is the SConstruct script that builds the project:

# Basic project main SConstruct script
# Copyright 2014 The Ostrich
# Author: ItamarO

Program('bin/writer',       # Output executable
        ['Writer/writer.cc', 'AddressBook/addressbook.cc'],
        CPPPATH=['#'])      # Allow including from project base dir

It’s simple enough. A single Program target is defined – bin/writer.

Building the project is as easy as running scons in terminal:

itamar@legolas sconseries (episodes/01-basics) $ scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
g++ -o AddressBook/addressbook.o -c -I. AddressBook/addressbook.cc
g++ -o Writer/writer.o -c -I. Writer/writer.cc
g++ -o bin/writer Writer/writer.o AddressBook/addressbook.o
scons: done building targets.

A couple of observations:

  • SCons compiled source files into intermediate object files on its own. The object files were created next to their respective source files, in the module directories.
  • Since I’m including h-files relative to the project base directory, I had to set CPPPATH to # (in SCons path variables, # is used to refer to the top-level SConstruct directory).
  • SCons does not rebuild targets if there’s no need to:
    itamar@legolas sconseries (episodes/01-basics) $ scons
    scons: Reading SConscript files ...
    scons: done reading SConscript files.
    scons: Building targets ...
    scons: `.' is up to date.
    scons: done building targets.
    
  • SCons does not use just the last modified timestamp to decide whether a file has changed:
    itamar@legolas sconseries (episodes/01-basics) $ touch AddressBook/addressbook.cc
    itamar@legolas sconseries (episodes/01-basics) $ scons
    scons: Reading SConscript files ...
    scons: done reading SConscript files.
    scons: Building targets ...
    scons: `.' is up to date.
    scons: done building targets.
    
  • I did not need to mention AddressBook/addressbook.h anywhere. But SCons knows that if it changes then things need to be rebuilt:
<< Edit a *comment* in AddressBook/addressbook.h >>
itamar@legolas sconseries (episodes/01-basics) $ scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
g++ -o AddressBook/addressbook.o -c -I. AddressBook/addressbook.cc
g++ -o Writer/writer.o -c -I. Writer/writer.cc
scons: done building targets.

– Note that SCons rebuilt the object files whose source files directly included the edited h-file. But SCons did not rebuild bin/writer, because the binary content of the object files did not change!
– I did not need to explicitly write anything related to cleanup. SCons takes care of it on its own, using scons -c:

itamar@legolas sconseries (episodes/01-basics) $ scons -c
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Cleaning targets ...
Removed AddressBook/addressbook.o
Removed Writer/writer.o
Removed bin/writer
scons: done cleaning targets.

– Such a small project doesn’t take much time to build. But even here, scons -j N accelerates the build by using parallel processes:

itamar@legolas sconseries (episodes/01-basics) $ scons -c
<< ... >>

itamar@legolas sconseries (episodes/01-basics) $ time scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
g++ -o AddressBook/addressbook.o -c -I. AddressBook/addressbook.cc
g++ -o Writer/writer.o -c -I. Writer/writer.cc
g++ -o bin/writer Writer/writer.o AddressBook/addressbook.o
scons: done building targets.

real     0m2.657s
user     0m0.944s
sys     0m0.987s
itamar@legolas sconseries (episodes/01-basics) $ scons -c
<< ... >>
itamar@legolas sconseries (episodes/01-basics) $ time scons -j 8
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
g++ -o AddressBook/addressbook.o -c -I. AddressBook/addressbook.cc
g++ -o Writer/writer.o -c -I. Writer/writer.cc
g++ -o bin/writer Writer/writer.o AddressBook/addressbook.o
scons: done building targets.

real     0m1.092s
user     0m1.038s
sys     0m0.210s

Summary

That’s my simple (AKA stupid) C++ example project with SCons build. The project doesn’t do much, but it’s non-trivial enough to demonstrate simple SCons use-case and a few interesting observations.

This is just a baseline of vanilla SCons capabilities. Contrast it with future posts in my SCons series.

The next post will describe using SCons in a multi-directory project layout with a central SConstruct at the root.

No Comments Yet.

Leave a Reply