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#
(inSCons
path variables,#
is used to refer to the top-levelSConstruct
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. ButSCons
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.
Leave a Reply