Modularizing pp6calculator and Automating its Build With Ben Morgan

advertisement
Modularizing pp6calculator and Automating
its Build With
Ben Morgan
Developer
Workflow
Build and Test
git commit
$ g++ pp6calculator.cpp -o pp6calculator
$ ./pp6calculator
“Add CMake build”
Edit Sources
Add Files
Building a C++ based Executable
Math.hpp
Math.o
Math.cpp
Monolith.cpp
MLMain.cpp
Monolith.exe
MLMain.o
Physics.cpp
Physics.o
Physics.hpp
Defeat in Detail - Modularize and Automate
«Math.hpp»
«Math.cpp»
//! /file Math.hpp
// "Header Guard"
// We can only have one declaration
// of any signature, so we use the
// preprocessor to conditionally
// include the code block between the
// #ifndef/#endif blocks once only
//
#ifndef PP6CALCULATOR_MATH_HPP
#define PP6CALCULATOR_MATH_HPP
// - Math.cpp : Implementation
// #include the header so declarations
// are matched
#include “Math.hpp”
//! The add function signature - its
`declaration`
int add(double a, double b,
double& answer);
// Implementation of add - its
`definition`
int add(double a, double b,
double& answer) {
answer = a + b;
return 0;
}
// Further definitions can follow...
// Further declarations can follow...
#endif // PP6CALCULATOR_MATH_HPP
Modularization - Headers and Sources
$ g++ pp6calculator.cpp -o pp6calculator
$ ./pp6calculator
$ git add pp6calculator.cpp
$ git commit -m “A change”
$ vim pp6calculator.cpp
$ g++ pp6calculator.cpp -o pp6calculator
$ ./pp6calculator
$ git add pp6calculator.cpp
What
about -m “More changes”
$ git
commit
diļ¬€erent
compilers
$ vim
pp6calculator.cpp
and/or flags?
$ g++ pp6calculator.cpp -o pp6calculator
What happens
$ ./pp6calculator
when we begin
$ git add pp6calculator.cpp
modularizing?
$ git commit -m “Yet more changes”
$ vim pp6calculator.cpp
...
Is it really so awkward?
What about
other users?
What about system
specific features?
«configure»
Find needed tools
Adapt code for system
Generate build task
«build»
Compile code
Link binary
Test binary
Build Automation
«install»
Install binary on system
Create installer for other
systems
GNU Make/Autotools
Qt qmake
Tools for Build Automation
pp6calculator.git
README.md
pp6calculator.cpp
CMakeLists.txt
Makefile
pp6calculator.xcodeproj
CMake - A “Metabuild” System
pp6calculator.sln
pp6calculator.git
README.md
pp6calculator.cpp
CMakeLists.txt
pp6calculator.DBG
pp6calculator.build
$ cmake \
../ppcalculator.build
Makefile
CMakeCache.txt
cmake_install.cmake
CMakeFiles/
$ cmake -DDEBUG=ON \
../ppcalculator.build
CMake Workflow - Separation of Source and Build
Modularization and CMake-ification of pp6calculator
• In the following walkthrough, we’ll modularize pp6calculator into a main
application plus units of functionality, introducing software libraries. We’ll
see how this benefits the ease of use and maintenance of the code, but also
how it makes building pp6calculator more awkward. We’ll bring in CMake to
help automate this and integrate it to our workflow to make it more
streamlined and robust. Finally, we’ll see how we can package pp6calculator
for use by others. Of course, we’ll continue to tracks changes using git!
• Modularize pp6calculator into a main executable and a support library
• CMake syntax and workflow for building/linking executables and libraries
• Use of CPack to generate source and binary packages
Tools you’ll need
Walkthrough From Scratch
• If you’re still working on your code from this morning and don’t want to
disrupt its development, you can instead clone our repository from github and
use that as a starting point.
• To do this, use the following commands to clone it and create a backup (NB,
this should all be done outside your working copy!!)
$ cd $HOME
$ mkdir mpagspp6-upstream && cd mpag6pp6-upstream
$ git clone git://github.com/drbenmorgan/pp6calculator.git \
pp6calculator_dbm.git
$ cd pp6calculator_dbm.git
$ git checkout -b mysolutions
$ git branch
• You’ll working on the “mysolution” branch. The “master” branch will
follow my solutions. We’ll see how to compare them later.
1: Recap on Project Structure
Remember how on Day 1 we recommended creating a clean directory in which to further create
a subdirectory for your git repository to reside? My setup and a tree of the structure is shown
below left. The reasons for this layout will become clear today, so if you don’t have this
arrangement, now is the time to create it!
In the worst case, simply commit all your local changes, push them to github and finally clone
the github repository into its new location.
Notes
Once you start to manage
many working copies of
projects, you may want to
create a dedicated directory
in your home directory to
hold these.
You may also wish to further
partition using Reverse-DNS
directories, e.g. ch.cern/
for CERN work,
com.github/ for your
projects.
2: How Many Lines?
If you look a the current implementation of pp6calculator, it’s starting to get a little unwieldy. It’s
406 lines long, with all the functions at the top of the file, and the main program at the bottom.
That’s not too bad in the big scheme of things, but we’d like to partition the functionality a bit
better as we know we’re going to add new features as we go through the course.
We’re going to start by looking at function declarations (or prototypes)
Notes
You will find code which has
source files composed of
thousands of lines!
Studies of human cognition
suggest that our working
memory can hold “7±2”
things at once. This can be
a useful guide as to how
long a typical function or
scope block should be.
3: Function Declarations
Before we can use a function in C++, it must be known to the compiler. You might have seen
this already when using std::cout if you forgot to include the iostream header.
In your pp6calculator program, move all of the math/array functions you’ve
implemented to the end of the .cpp file so that the main function is the first that
appears in the file. Try recompiling - does it work?
Notes
If you’ve begun
modularizing the code
already, then you can skip
this step, but be aware of
where the function
declarations you use appear
to the compiler when
they’re #included from
another file.
4: No Declaration, No Go
You’ll have found the compilation fails with many “not declared in this scope errors”.
As you’ll have guessed from earlier discussion, this is due to the use of a function that is
unknown to the compiler at the point the use is encountered.
We could fix this by moving the functions back to before main, but instead we’ll declare them
before main, and leave the definitions(implementation) where it it.
Notes
We’ve used the terms
function declaration and
function prototype
interchangeably here. For C
++ this is o.k., but in older
style C there is a difference!
Here, you can declare a
function without specifying
the types of the arguments.
A prototype is a declaration
that includes the number
and type of its arguments.
5: Declaring Functions
To declare a function, we simply specify its return type, name and the number and types of the
arguments it takes, omitting the actual body, i.e. implementation, of the function. The declaration
tells the compiler about the function interface, and promises that its implementation will be found
“somewhere else”. Add declarations for your functions at the beginning of your main
program, using my example for the add function of pp6calculator in the left hand tile
below. Check that you can now compile and run the program o.k.
Notes
Function declarations are
our first concrete use of an
interface. The key point to
grasp is that an interface
frees us from worrying how
a task is done, just that it is
done.
The implementation might
be intellectually interesting,
but knowledge of it is not
needed to use the interface.
6: Handling Link Errors
One issue you might see with separate declarations and definitions are errors occurring at link
time, i.e. you have syntactically correct code, but something goes wrong when the linker tries to
produce the final executable.
Comment out one of your function definitions, but leave its declaration and usage in
place. You should see an error about a missing or undefined symbol. This illustrates
that a declaration is just a hint, with no guarantee that an implementation exists.
Notes
Link errors can be
significantly more difficult to
track and resolve than
compiler errors. This is
partly due to their more
cryptic nature, and also
because you know your
code compiles o.k.!
Generally, the undefined
symbol error seen here is
most common.
7: A Math Header File
The preprocessor #include statements we’ve seen so far perform a verbatim include of the
referenced file. We can therefore begin to tidy up the main pp6calculator.cpp file by moving
the function declarations to a new header file, say PP6Math.hpp, and then #includeing this.
Move your function declarations out of your main .cpp file and into a .hpp file, then
#include this into the main .cpp file. Compile as before, and it should just work…
Notes
Not much to say here, other
than can you spot, or know,
the further steps we’ll need
to make to integrate
everything nicely and
prevent errors?
8: Moving Definitions into the Header
Moving the function declarations into a header file has tidied up the main file, but we’ve left the
function definitions in there, creating a bit of a disconnect.
The easiest thing to do for now is to move the function definitions out of the main file
and into your header file after the declarations. After doing this, check that you can still
compile pp6calculator o.k. If so, what happens if you now #include your header file
twice as shown in the left tile below?
Notes
Having declarations and
definitions in the same
header is usually a bad idea,
and we’ve done it here to
illustrate an error case.
There are however some
instances where you will
want definitions in a header.
Performance is one reason,
but beware premature
optimization!
9: The One Definition (to) Rule (them all)
When you #included your header file twice and recompiled, you’ll have probably seen a
stream of “error: redefinition of” errors. The reason is that by #includeing the
header twice, the compiler sees two definitions of each function in a single translation unit.
The C++ Standard states that there can be no more than one definition in any translation unit.
In this case the error is rather artificially and deliberately induced, but the next step will show how
we can fix this automatically.
Notes
If you didn’t see an error,
then you probably already
know how to fix it.
If not, can you see what you
did in the header file to
prevent the ODR causing
errors?
What would you have to
change to see the error?
10: Include Guards
You might think the solution is obvious: “just don’t #include the header twice”. True, and the
problem is that as soon as the number of files/headers increases, it becomes impossible to keep
track of who #includes what from where, so any double inclusion can lead to horrifically
nested errors… Instead we let the compiler/preprocessor do the work - using an include guard
Enclose the code (declarations and definitions) in your header in the #ifndef/
#define/#endif block as shown in the left hand tile below and try recompiling.
Notes
All the guard does is
prevent the code being
included into the same
translation unit more than
one.
Note that the identifier of the
#define must be unique.
PROJECT_HEADER_HH is
usually sufficient, though
some add long random
hashes as well!
11: A Math Source File
In compilation terms we haven’t really changed anything - the inclusion of the header by the
main file simply results in the same translation unit as our original monolithic main file. In line with
our wish for the header to be a true interface, we want to move the function definitions into their
own dedicated .cpp file.
Create a new .cpp file to hold your function definitions, giving it the same name as the header,
e.g. Math.cpp for Math.hpp. Think about how to compile the overall pp6calculator
Notes
As shown on the left, the
cpp file does not need
header guards, but you do
need to #include the
header contain the function
declarations about to be
defined.
You may also need to add
(and maybe remove) the
inclusion of some Standard
Library headers.
12: Compiling Multiple Sources
In fact at the moment this is very simple - we simply list all the sources going into the application
in the compile command:
$ g++ PP6Math.cpp pp6calculator.cpp \
-o pp6calculator
Notes
You can also use wildcards,
e.g. “*.cpp”.
The downside of listing all
files is that we recompile
everything every time. For
example, we might make a
minor change to
PP6Math.cpp, but we’d
still be compiling
pp6calculator.cpp,
even though nothing in it
had changed.
13: Project Organization (Again!)
As it stands, our source code is now in reasonable shape, however at the file level we’ve still got
a flat organization of files. As you would with other files, it best to use directories to file the
sources into logical collections. Create a directory to hold your header and source file, and
use git mv to relocate them as shown in the tiles below
$ git mv <file> <destination>
Notes
Again, this is an area where
there’s no hard and fast
rule. If it’s your own project
you can organise as you
wish, though logical
grouping of files will help to
ease the management task.
On large project, you may
have a set of rules to follow!
14: Complicated Compilation
This reorganization of files means we now need to set another compiler flag. If you try compiling
the sources again, you’ll get an error about your header file being missing. To tell the g++
compiler where to look, we need to pass it the -I flag and an argument indicating the absolute
or relative (to the directory where you run the compiler) path to a directory where headers may
be found. You can pass as many -I flags as needed.
Notes
The compiler has a default
search path which you can
see by running g++ with the
-v flag. Any paths added
through -I flags are
searched before these
default paths.
Your #include statements
need to list the header using
a (relative) path that when
added to one of the paths
will point to the header.
15: Introducing CMake
• We’ve seen some of the problems with compilation that occur when we start
to move beyond non trivial programs
• We have to list all the files (can use wildcards)
• We always recompile everything, rather than just the files that have
changed.
• In fact, it’s worse than this because whilst we know which files have
changed, we not have complete knowledge of which files depend on
the changed files, and hence need recompilation.
• We have to input all the include paths.
• In the next section of the walkthrough we’ll use CMake to resolve these
16: Getting Help With CMake
There’s plenty of documentation on the CMake website, and of course man cmake will give a
good overview. For quick lookup of the commands, variables and properties that comprise
CMake’s scripting language, it’s hard to beat CMake’s command line help interface:
$ cmake --help-command project
Useful Help Commands
--help-variable NAME
Print help on variable
--help-property NAME
Print help on property
--help-<type>-list
List names of available
<type> (command,
variable or property)
--help-commands
List help for all
commands
17: A First CMake Script for pp6calculator
The first step is to open a text file named CMakeLists.txt in the top level of the project you want
to build. In our case, the top level directory is the same as our git repository. CMake requires the
file to be named CMakeLists.txt.
To begin with we simply add two commands, one to check we’re running a suitable version of
CMake, and one to perform minimal system setup for our project.
Notes
You can supply more
detailed version numbers to
cmake_minimum_required if
you need specific versions.
Kitware provide a Version
Compatibility Matrix on their
wiki
You can choose the name
of the project as needed.
Use cmake --helpcommand for more info!
18: Creating the Build Directory
As mentioned earlier, a good practice is to perform the build in a separate directory. Even though
our current CMake doesn’t build anything yet, we’ll get this directory set up first to illustrate the
usage of CMake.
Create a build directory for pp6calculator outside of your repository. The easiest thing
to do is create this directory parallel to the repository as shown below:
Notes
You can create the build
directory wherever you
want. The parallel creation
pattern is simply a
convenience and the
author’s personal
preference.
19: Using CMake to Generate Makefiles
With the build directory created, we can now run CMake to generate the build scripts. On UNIX
systems, CMake generates Makefiles by default. To see the other build systems supported by
CMake, see the “Generators” section of the CMake man page. To generate the Makefiles, simply
run the cmake command in your build directory, and pass it the path to your source directory,
i.e. the directory holding the CMakeLists.txt file you wrote.
Notes
You can supply the path as
a relative path from your
build directory to the source
directory, or as a full
absolute path.
The output you see when
running CMake will vary
between systems, but you
should not see any errors at
this point!
20: What CMake has Done
Even though we’ve only got two commands in our CMakeLists.txt script, running CMake has
performed quite a bit of configuration for us already. If you look at the output from your run of
CMake, you should see that it’s searched for, found and checked the functionality of C and C++
compilers for us.
It’s also written out the configuration for use, and we’ll look at that in the next step.
Notes
You can get extremely
detailed information on the
configuration process by
running cmake with the -trace option.
You can run cmake as
many times as you want try this and note that it does
not perform all steps again.
It has cached information.
21: What CMake has Generated
Once you have run CMake successfully, ls the contents of the build directory, and you’ll find
several files and directories created by CMake. The directory CMakeFiles contains all
temporary files and checks needed by CMake, and we won’t worry about this for now.
The key files to look at are Makefile, the make build script that’s been generated, and
CMakeCache.txt, CMake’s cache of settings for this build.
Notes
We’re ignoring the
cmake_install.cmake
file for now. We’ll come
back to this when we
package up the project.
22: CMakeCache.txt
This file is used by CMake to persist settings like the compiler and compiler flags so that these
do not need to be checked for every time CMake runs. It’s just a text file so you can view it in a
pager like less. To explore and edit the cache, you can use the ccmake ncurses interface.
In your build directory, type ccmake . to start up the CMake curses frontend. Press ‘t’
to show all of the cache entries and use the up and down arrow keys to navigate the
list. Press ‘q’ to quit at any point.
Notes
You can also use ccmake
from the start to configure
your project. Simply run
ccmake and pass it the
path to your source
directory, just as you did
with cmake earlier.
Note that each option is
documented in the console
bar at the bottom.
23: Makefile
This is the script generated by CMake for use with the make build tool. It’s a text file which you
can view with a pager like less, and we’ll look at the format of the file in the next step.
To use the script, simply type make, and you’ll find it doesn’t do much. CMake
generated Makefiles provide a standard interface to Make. To see in detail what Make
is doing, try make VERBOSE=1. You can also use make help to see the list of “targets”
available. Try building each target by running make <targetname>
Main Targets
all:
Build everything, the
target built if you just type
make.
clean:
Remove all built files
rebuild_cache:
Force rerun of CMake
edit_cache:
Start up ccmake
24: An Aside on Makefiles
The Make build tool is based on the concept of rules, which link a target (a “thing” to be “built”)
to the “things” it depends on and the commands needed to “build” the target. Makefiles are just
scripts listing these targets and also any needed variables and other functionality.
The key thing to note are the dependencies of a target. If the target is newer in time than its
dependencies (this assumes target/dependencies are files), then the commands won’t be run.
This solves the issue we noted earlier about manual builds always recompiling all files!
Notes
Targets can depend on
other targets, so you can
chain them together easily.
There can also be targets
which are just symbolic
names for tasks. We’ve
seen this already with the
“clean” target.
See the GNU Make Manual
for further information.
25: Getting CMake to Build pp6calculator
Returning to CMake, open your CMakeLists.txt and add the commands shown in the
tile below left, adapting them for your organization of sources.
include_directories will make CMake configure the compiler to look in the supplied
directory for header files. If a relative path is supplied, it is taken as relative to the directory
holding the CMakeLists.txt which called include_directories.
add_executable configures the build of the program, we supply its name and all its sources.
Notes
It might seem odd to put a
header file into the list of
sources for a program. We
don’t have to add the .hpp
files because CMake will
track dependencies
automatically. However, if
we want to use an IDE like
Xcode, then the header
won’t appear in the IDE
solution unless we list it.
26: Building pp6calculator
Change back to your build directory (where you ran CMake before). If you already ran
cmake in here, simply type make and you should find that CMake automatically reruns,
before building pp6calculator.
Try running make again, and you should find nothing changes. Then run make clean
and then make again. To see exactly how pp6calculator is built, try make clean
followed by make VERBOSE=1.
Notes
CMake generates build
scripts that also track
changes to your CMake
build scripts. Thus you don’t
need to rerun CMake all the
time, just run make!
If you do need to start from
scratch you can simply
remove CMakeCache.txt
and rerun cmake, or just
remove the build directory.
27: Building pp6calculator Fast
Make, and CMake generated Makefiles support parallel builds - the ability to use the multicores
on modern systems to speed up builds by compiling different pieces of code concurrently. Of
course, this is aided by modularization of code. With make, simply use the -jN argument:
$ make -j2
Note
Generally, setting N to the
number of cores is
sufficient, though you
should be aware of
resource limitations on
multiuser machines.
There are also systems for
compiling on clusters, such
as distcc.
28: Build Modularization: Libraries
We’ve modularized the source code for pp6calculator, but we’re still building a monolithic
application. As it turns out, we’ll continue to do this, but it’s useful to see how we can use
CMake to partition the build into separate components of binary code - libraries.
Use the add_library command to create a library from the headers/sources other
than your main program. Change add_executable to only take the source for the
main program. Finally, use target_link_libraries to link the library to the program.
Notes
Note that in
target_link_librarie
s we use the symbolic
names for the program and
library. These are internal to
CMake and, as we’ll see,
don’t correspond to the
names of the resultant files
on disk.
29: Static Libraries
With your CMakeLists.txt file edited, go back to your build directory (a good tip is to
keep two terminal windows open!) and run make again. CMake should reconfigure and
rebuild, leaving you with pp6calculator plus a new file, libpp6lib.a, a static library.
This contains the pre-compiled code for the math functions, and by linking it into
pp6calculator, we have copied the needed blocks of binary into the executable. This makes
the binary larger, but as we are only using the library in a single executable, this is o.k.
Notes
To use any library, you
simply need to #include the
relevant header file(s) with
the declarations of the
library interface in your
source code. You then
simply link the library to your
final executable.
It’s this feature that leads to
reusable code!
30: Shared Libraries
By default, CMake builds static libraries, but we can also get it to build and use shared,
or dynamic libraries. To do this, add the SHARED argument to your add_library
command as shown below.
In your build directory, run make clean followed by make. You should have pp6calculator as
before, and a different library, libpp6lib.so. Note that the tile below shows a different name,
libpp6lib.dylib. Different platforms have different extensions for shared libraries!
Notes
You can also change the
type of library built by
default using the cmake
BUILD_SHARED_LIBS
variable passed as an
argument, e.g.
$ cmake \
-DBUILD_SHARED_LIBS=ON \
../pp6calculator.git
31: Using Shared Libraries
Once you’ve got your shared library built, run ldd pp6calculator on Linux, or otool -L
pp6calculator on OS X. These commands show the shared libraries used by a program.
Shared libraries still contain binary code and are used in C++ code in the same way as static
libraries. The difference arises in the linking step, with shared libraries being loaded into the
executable at runtime. The linker uses an addressing scheme so the code used by the
application can be located at startup. Many applications can use the same shared library.
Notes
Shared libraries are the
preferred mode for system
libraries because they can
be upgraded dynamically.
Applications using static
libraries must be recompiled
when the library changes.
In our case though, pp6lib is
private to the application, so
static libraries are actually
better because we do not
have to worry about shared
library lookup issues.
32: Back to Static Modules
Because pp6lib is private to our application, i.e. it’s not intended for third party use, we’ll force
it to be a static library, and also modularize the build further using add_subdirectory.
Create a new CMakeLists.txt file in the subdirectory holding your library code, and
move the add_library command into this file, adjusting the file paths and using the
STATIC flag. Use the add_subdirectory command in the top level CMakeLists.txt
to make CMake recurse into the library subdirectory. Rebuilding should work!
Notes
You can add arbitrary levels
of nesting to CMake.
However, beware that too
many levels may result in
slower build performance.
You can of course always
have a single top level
CMakeLists.txt script
and directly refer to
everything here.
33: Compiler Flags
When you ran make VERBOSE=1 earlier, you may have noticed that CMake did not use some
of the useful compiler flags we’ve seen earlier like -W -Wall. You can change the flags used by
setting the CMake variable CMAKE_CXX_FLAGS. As flags are compiler specific, we need to
guard the setting of this variable by a check on which compiler is used.
We’re using the GNU compiler, so set CMAKE_CXX_FLAGS to the set shown below, using
the CMAKE_COMPILER_IS_GNUCXX variable to guard the setting.
Notes
Of course, check that the
flags are passed to the
compiler by using make
VERBOSE=1
The list of flags we’ve
chosen provides very strict
checking for errors and
compliance with ISO C+
+98. You can get more
details on each flag in the
man page for g++.
33a: Compiler Flags: C++11
In general, compilers do not default to using C++11, even if they support it, so an additional flag
is needed to select the standard. We saw this for GCC/Clang in the previous slide with the std=c++98 flag. To enable C++11, one could just change this to -std=c++11, but the
compiler would need to support this. The example below shows how we can choose to enable
C++11 support if it is available in the GCC or Clang compiler being used.
Notes
Of course, check that the
flags are passed to the
compiler by using make
VERBOSE=1
Note that this is not the
whole story of enabling
support for C++11. You
may need to consider which
features are supported, and
the Standard Library in use
(e.g. libstdc++ vs libc++).
34: Finishing Up: Packaging and Installing
As it stands, our CMake scripts build everything for us but we can’t install pp6calculator for
later use or by others.
To enable this, use the install command and include CMake’s CPack module as
shown below in your CMakeLists.txt and rebuild. You should now have two new
make targets, package and package_source. Build them and see what’s generated.
Notes
CMake’s CPack module has
lots of settings to help
configure what is packaged
and how the packages are
named. To see the CPack
variables you can set in your
CMakeLists.txt, run cpack
--help-variables
There’s also a helpful, if
basic, tutorial on CPack on
the CMake wiki.
35: A Note on Installing
The addition of the install command means that you can install pp6calculator directly to
the system using make install. However, if you try this, you’ll probably get a permissions
error because CMake’s default directory for installation is /usr/local.
Try changing CMAKE_INSTALL_PREFIX to say, ~/Software, by running make
edit_cache and editing its entry. Reconfigure, rebuild and try running make install.
Notes
Rather than using
edit_cache, you can also
just run cmake again and
pass
CMAKE_INSTALL_PREFIX
as a command line
argument, e.g.
$ cmake \
-DCMAKE_INSTALL_PREFIX=~/
Software \
../pp6calculator.git
Walkthrough Summary
We’ve worked through the steps to modularize both the source code and build of pp6calculator.
We’ve seen how to separate functions into declarations and definitions using header and
source files. Header files and function declarations have provided our first example of an
interface.
We’ve used CMake to automate the build of pp6calculator from its modularized sources, and
have introduced code libraries to modularize the build. We also packaged up pp6calculator.
Further Reading
CMake Documentation
Software
Build Systems
Textbook
Comparing your work with upstream
• If you cloned my repository to work with, you’ll have made and committed
changes yourself to your local clone on the “mysolutions” branch
• If you’re curious to compare your solutions with mine, then you can do the
following to pull changes and compare:
$
$
$
*
git checkout master
git pull origin
git branch
master
mysolutions
$ git diff master mysolutions
Day 2 Homework: Aims
• Simplify menu interface of pp6calculator to partition functionality and allow
for future extensions
• Provide an operation to generate 100 random energies and momenta and
report the mean energy, its standard deviation and the energy and momentum
of the highest energy particle.
• Provide an operation to analyse a data file provided by the user listing particle
type, energy and momentum
• Pick out mu+ and mu- particles observed in “run4.dat”
• Calculate the invariant mass of each mu+/mu- combination and report the
largest 10 invariant masses and which combinations formed these
Day 2 Homework Part 1
• Provide a two level menu interface for pp6calculator
• The top level menu should allow the user to go into one to N submenus of
operations, partitioned by the day of the course they were implemented
on.
• Each submenu should provide an interface to the operations for a given
day of the course, essentially the same interface you implemented on Day
1.
• Hint: Use functions and headers to modularize the code between the top level
application, menu interface and operation implementation.
• Hint: Having said that, use your judgment as to how much or how little to
modularize, and experiment with this.
Day 2 Homework Part 2
• Provide an operation to generate 100 random energies and 3-momenta and
report the mean energy and its standard deviation
• Hint: To generate (pseudo)random numbers in C++, use the following:
#include <cstdlib> // At the top
...
int a = rand()%100 // Random number between 0-99
• Hint: Start by generating the momentum components, and then generate a
random mass to calculate the energy.
• Hint: Think about how to adapt your sort function so that it will return an array
whose entries are the indices of the sorted entries in the main array. For
example, if the input array was {2,5,3,7}, the index array would contain
{3,1,2,0}.
Day 2 Homework Part 3
• Provide an operation to analyse a data file provided by the user listing particle
type, energy and momentum
• Pick out mu+ and mu- particles observed in “run4.dat”
• Calculate the invariant mass of each mu+/mu- combination and report the
largest 10 invariant masses and which mu+/mu- events formed these
• The data file to be analysed can be downloaded from the webpage for Day 2.
• The filename is “ObservedParticles.dat”, it’s a simple text file so the format
can be read directly
Day 2 Homework Part 3
• To help with reading the file, you should use the FileReader library supplied
on the course webpage.
• You can either copy the sources files into your library, or unpack the
FileReader source package into your repository and use CMake’s
add_subdirectory and target_link_libraries just as you do for
your library.
• See the README.md file in the FileReader package for documentation
on how to use it to read files and extract values from each line.
• Hint: Note that by forming pairs, we mean calculate the invariant masses
formed by pairing each mu- with every other mu+ in sequence (i.e. it’s
combinatoric). Use the index based sort you looked at in Part 2 to present the
10 highest invariant masses and their associated mu+/mu- event pairs
Download