How to make a project

Warning

This document is old and crufty. While the Implementation section below is generally useful, this document as a whole should be taken as nothing more than a rough guide.

CMake

  • A makefile generator

  • Like autoconf, but better

  • Uses it’s own private language (bad, but not horrible)

Project Layout

/
  CMakeLists.txt
  python/
      __init__.py
  private/
      modulename/
          Module.cxx
      pybindings/ (optional)
      converter/ (optional)
  public/ (optional)
      modulename/
           PublicInterface.h

IceCube CMake macros

i3_project

i3_project(project_name
           DOCS_DIR resources/docs
           PYTHON_DIR python
          )

i3_add_library

(First):

Name of library

(N args):

Names of files

USE_TOOLS:

external tools

USE_PROJECTS:

icetray projects

i3_add_library(lib_name
               private/modulename/file1.cxx private/modulename/file2.cxx
               USE_TOOLS boost gsl root
               USE_PROJECTS icetray dataio dataclasses
              )

i3_add_pybindings

(First):

Name of project (should match directory name)

(N args):

Names of files

USE_TOOLS:

external tools

USE_PROJECTS:

icetray projects

i3_add_pybindings(project_name
                  private/pybindings/file1.cxx private/pybindings/file2.cxx
                  USE_TOOLS boost gsl root
                  USE_PROJECTS icetray dataio dataclasses
                 )

i3_test_scripts

This registers scripts with the test system and automatically runs them on the build bots. Check https://github.com/icecube/icetray/actions/ after every commit to see if you broke anything on other platforms.

It’s common to include the following in your CMakeLists.txt file. This will automatically register every python script in ‘resources/test’.

i3_test_scripts(resources/test/*.py)

CMake syntax

sh-like syntax:

  • SET(VARIABLE_NAME “VALUE”)

  • message(STATUS “A status message: ${VARIABLE_NAME}”)

  • colormsg(CYAN “ +- Project is not found. Skipping”)

Conditional Code

If you can, optionally include parts of a module that require extra things.

Use AND and OR for boolean logic.

ex:

if(SUITESPARSE_FOUND)
    # do things that require SuiteSparse
else(SUITESPARSE_FOUND)
    colormsg(CYAN " +- SuiteSparse not found")
endif(SUITESPARSE_FOUND)

Trivial Pybindings

If you don’t have real pybindings, use this.

Note

Don’t mix this form with real pybindings.

In python/__init__.py:

from icecube._project_name import *

Documentation

Note

Please document your project. Besides being a good idea, it is a requirement for any serious project.

Doxygen documentation

Documents the C++ code. Decent out of the box, but we can help it along.

Note: most comments are more appropriate for implementation files. We’d actually like to read the header files instead of lines of comments. (no one really reads the implementation files though).

The basic comment:

/**
 * This is a function that finds the sqrt of a number
 */
double Sqrt(double n) {
    return sqrt(n);
}

Build the docs:

$ make docs

This will also make sphinx docs.

Sphinx

Documents the Python code. Better styling.

Use restructured text (.rst). Sphinx interprets this to make html.

Modify CMakeLists.txt:

i3_project(advanced_bootcamp
    PYTHON_DIR python
    DOCS_DIR resources/docs
)

Build only the Sphinx docs:

$ make html

Section Syntax:

# with overline, for parts
* with overline, for chapters
=, for sections
-, for subsections
^, for subsubsections
", for paragraphs

https://www.sphinx-doc.org/

Implementation

Let’s implement the basic framework of a module:

http://code.icecube.wisc.edu/svn/sandbox/advanced_bootcamp

Public Header

public/advanced_bootcamp/I3Bootcamp.h:

// include guard
#ifndef BOOTCAMP_H
#define BOOTCAMP_H

#include <icetray/I3FrameObject.h>
#include <icetray/serialization.h>
#include <string>

// subclass I3FrameObject so we can insert this class into the frame
class I3Bootcamp : public I3FrameObject {
public:
    // interface goes here
    std::string text;
    int number;
    float number2;
private:
    // basic boost serialization
    friend boost::serialization::access;
    template <typename Archive>
    void serialize(Archive &ar, unsigned version);
};

// icetray macro to make pointer typedefs for I3BootCampPtr, etc
I3_POINTER_TYPEDEFS(I3Bootcamp);

#endif // BOOTCAMP_H

Private Modules

private/advanced_bootcamp/I3Bootcamp.cxx:

// the module interface
#include <advanced_bootcamp/I3Bootcamp.h>

// do serialization (write to/read from an i3 file)
// the version number can be used to establish version formats
template <typename Archive>
void I3Bootcamp::serialize(Archive &ar, unsigned version)
{
    // convince the serializer that we are an I3FrameObject
    // the & operator is both read and write
    // make_nvp (name,value pair) allows both binary and xml output
    ar & make_nvp("I3FrameObject", base_object<I3FrameObject>(*this));

    // now actually serialize our contents
    // all default types and I3 types are serializable
    ar & make_nvp("text", text);
    ar & make_nvp("number", number);
    ar & make_nvp("number2", number2);
}

// another icetray macro to do most of the heavy lifting
// for serialization
I3_SERIALIZABLE(I3Bootcamp);

private/advanced_bootcamp/I3BootcampModule.cxx:

// some basic includes
#include <icetray/I3ConditionalModule.h>

// the module interface
#include <advanced_bootcamp/I3Bootcamp.h>

// let's make a private module
class I3BootcampModule : public I3ConditionalModule {
public:
    // the constructor just calls the parent
    I3BootcampModule(const I3Context &ctx) : I3ConditionalModule(ctx) {}
    virtual ~I3BootcampModule() {}

    // process physics frames
    void Physics(I3FramePtr frame);
}

// use an icetray macro to make this work with icetray
I3_MODULE(I3BootcampModule);

void
I3BootcampModule::Physics(I3FramePtr frame)
{
    // make an I3Bootcamp object
    I3BootcampPtr output(new I3Bootcamp);
    output->number = 6;
    output->text = "Some text";

    // add the I3Bootcamp object to the frame
    frame->Put("BootcampStuff", output);
    // push the frame to the next module
    PushFrame(frame);
}

Pybindings

private/pybindings/module.cxx:

#include <icetray/load_project.h>

#include <public/advanced_bootcamp/I3Bootcamp.h>

// register function for the interface class
void register_I3Bootcamp()
{
    // use an alias instead of "using boost::python"
    // saves us from really strange errors
    namespace bp = boost::python;

    // make a boost::python class
    // the I3BootcampPtr came from I3_POINTER_TYPEDEFS
    bp::class_<I3Bootcamp, I3BootcampPtr, bp::bases<I3FrameObject> >("I3Bootcamp")
        // make a python value (read/write)
        .def_readwrite("text",&I3Bootcamp::text)
        .def_readwrite("number",&I3Bootcamp::number)
        .def_readwrite("number2",&I3Bootcamp::number2)
    ;
}

// an icetray macro around the boost::python messiness
I3_PYTHON_MODULE(advanced_bootcamp)
{
    // load the c++ library
    // second argument is false to be quiet
    load_project("advanced_bootcamp", false);

    register_I3Bootcamp();
}

python/__init__.py:

# load the c++ pybindings
from icecube._import advanced_bootcamp

CMakeLists.txt

i3_project(advanced_bootcamp)

i3_add_library(advanced_bootcamp
        private/advanced_bootcamp/I3Bootcamp.cxx
        private/advanced_bootcamp/I3BootcampModule.cxx

        USE_TOOLS boost python
        USE_PROJECTS icetray dataclasses
)

i3_add_pybindings(advanced_bootcamp
        private/pybindings/module.cxx

        USE_TOOLS boost python
        USE_PROJECTS icetray dataclasses
)

Usage

Let’s use this module to do something:

from icecube.icetray import I3Tray
from icecube import icetray, dataio, dataclasses, advanced_bootcamp

tray = I3Tray()
tray.AddModule('I3InfiniteSource', Stream=icetray.I3Frame.Physics)
tray.AddModule('I3BootcampModule')

def foo(frame):
    bootcamp = frame['BootcampStuff']
    print(bootcamp)
tray.Add(foo)

tray.AddModule('Dump')

tray.AddModule('I3Writer', filename='foo.i3')

tray.Execute()