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:
SConscompiled 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
CPPPATHto#(inSConspath variables,#is used to refer to the top-levelSConstructdirectory). SConsdoes 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.
SConsdoes 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.hanywhere. ButSConsknows 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.






Leave a Reply