A trivial BARS application

A bare bones example of a BARS application can be found in programs/example.

This app can:

  • parse options
  • read configuration file
  • check if an option has an acceptable value.
$ example
int option value: 1
string option value: test
$ example --test-int-param 23
int option value: 23
string option value: test
$ example --test-int-param -1
ERROR: ExampleIntParameter can't be -1
$ echo 'ExampleIntParameter 100' > /tmp/cfgexample; example --config /tmp/cfgexample
int option value: 100
string option value:

It consists of the following files:

  • main.cc - application logic.
  • opts.h, opts.cc - option parsing, configuration parsing, global variables and sanity checks.
  • CMakeLists.txt - build system commands. This file binds your app to the BARS build system. Do not touch it, unless you know what you are doing.

Let's go through each of these files in order.

main.cc

Right now main.cc looks pretty simple.

//program headers
#include "opts.h"

//system headers
#include "iostream"

//BARS dependencies
#include "BARS.h"

using namespace BARS;

int main(int argc, char** argv) 
{
    // Init should be called at the beggining of all BARS programms
    App::Init(argc, argv, "example.rc", parseOpts, readRC, checkParams);

    std::cout << "int option value: "    << gExampleIntParameter << std::endl;
    std::cout << "string option value: " << gExampleStringParameter << std::endl;

    return 0;
}

App::Init is a BARS function that sets up your application. It does the following:

  • Parses arguments with the parseOpts function.
  • Reads a configuration file (either default one, or the one provided by the user) with readRC
  • Checks if the app can run with checkParams. This includes:
    • Checking if all the necessary parameters have been provided.
    • Checking if all the required files exist.
    • If some parameters are not set with an option or a configuration file entry, initialize them with a default value. For example, if --out flag was not provided, application output can be set to stdout.

The functions parseOpts, readRC and checkParams are defined in the opts.cc file. Let's take a look at it.

opts.cc

Here's opts.cc from the example application.

#include "opts.h"

#include "iostream"
#include "getopt.h"
#include "cstdlib"
#include "stdlib.h"

#include "TEnv.h"

#include "BARS.h"

using namespace BARS;

int         gExampleIntParameter = -1;
std::string gExampleStringParameter;

/**
 * Read resource file, fill parameters.
 */
void readRC(const char* rcpath)
{
  TEnv env;    
  if (-1 == env.ReadFile(App::RCPath, kEnvAll)) {
    std::cerr << "ERROR: failed to read configuration file at '" << App::RCPath << "'" << std::endl;
    exit(1);
  }

  gExampleIntParameter    = env.GetValue("ExampleIntParameter",    gExampleIntParameter);
  gExampleStringParameter = env.GetValue("ExampleStringParameter", gExampleStringParameter.c_str());
}

/**
 * Parse options passed to the application.
 */
void parseOpts(int argc, char** argv)
{
  int c = -1;
  while (1) {
    static struct option long_options[] = {
      {"out",             required_argument, NULL, 'o'},
      {"season",          required_argument, NULL, 's'},
      {"run",             required_argument, NULL, 'r'},
      {"config",          required_argument, NULL, 'c'},
      {"iterations",      required_argument, NULL, 'i'},
      {"prodid",          required_argument, NULL, 'p'},
      {"in",              required_argument, NULL,  0},
      {"cluster",         required_argument, NULL,  2},
      {"test-int-param",  required_argument, NULL,  3},
      {"test-str-param",  required_argument, NULL,  4},
      { NULL, 0, NULL, 0 }
    };
    /* getopt_long stores the option index here. */
    int option_index = 0;

    c = getopt_long (argc, argv, "", long_options, &option_index);

    /* Detect the end of the options. */
    if (c == -1)
      break;

    switch (c) {
      case 0:
        App::Input = optarg;
        break;
      case 2:
        App::Cluster = atoi(optarg);
        break;
      case 3:
        gExampleIntParameter = atoi(optarg);
        break;
      case 4:
        gExampleStringParameter = optarg;
        break;
      case 's':
        App::Season = atoi(optarg);
        break;
      case 'r':
        App::Run = atoi(optarg);
        break;
      case 'c':
        App::RCPath = optarg;
        break;
      case 'p':
        App::ProdId = optarg;
        break;
      case 'i':
        App::Iterations = atoi(optarg);
        break;
      case 'o':
        App::Output = optarg;
        break;
      case '?':
        /* getopt_long already printed an error message. */
        break;
      default:
        abort ();
    }
  }
}


void checkParams() 
{
  if (-1 == gExampleIntParameter) {
    std::cerr << "ERROR: ExampleIntParameter can't be -1" << std::endl;
    exit(1);
  }
}

Global variables

The variables corresponsing to the application options are defined and initialized on top (don't forget to create externs in opts.h!). The example application has 2 of them.

int         gExampleIntParameter = -1;
std::string gExampleStringParameter;

Each option is prefixed with a small-case g.

BARS::App namespace has global variables for commonly used options (App::Input for input, App::Cluster for cluster # etc). See here for BARS::App documentation.

Parsing configuration

After option declarations goes the readRC function, used to parse an app configuration file:

void readRC(const char* rcpath)
{
  TEnv env;    
  if (-1 == env.ReadFile(App::RCPath, kEnvAll)) {
    std::cerr << "ERROR: failed to read configuration file at '" << App::RCPath << "'" << std::endl;
    exit(1);
  }

  gExampleIntParameter    = env.GetValue("ExampleIntParameter",    gExampleIntParameter)
  gExampleStringParameter = env.GetValue("ExampleStringParameter", gExampleStringParameter.c_str());
}

Each BARS application comes with a configuration file. It is a standard ROOT config file, as defined at here. A default configuration file can be found at $BARSSYS/resources/. Users can supply their own with --config /path/to/my/config.rc. Path to the configuration file is stored in App::RCPath.

Parsing options

The next part of opts.cc is the function parseOpts. It parses options provided to the application and assigns their value to option variables defined on top of the opts.cc. Option parsing is executed after configuration parsing, so options provided to a BARS application override the values provided by the configuration file.

parseOpts is a textbook function for argument parsing in Linux (see man getopt_long for reference).It starts with a struct defining all options for the application,

    static struct option long_options[] = {
      {"test-int-param",  required_argument, NULL,  3},
      {"test-str-param",  required_argument, NULL,  4},
      ...
      ...
      ...
      { NULL, 0, NULL, 0 }
    };

followed by the switch processing every option:

    switch (c) {
      case 3:
        gExampleIntParameter = atoi(optarg);
        break;
      case 4:
        gExampleStringParameter = optarg;
        break;
        ...
        ...
        ...
      default:
        abort ();
    }

Adding new options this way should be self-evident. Note that an example application has a lot of commonly used options already parsed for the developer's comfort.

Checking parameters

Finally, the last function in opts.cc is checkParams

void checkParams() 
{
  if (-1 == gExampleIntParameter) {
    std::cerr << "ERROR: ExampleIntParameter can't be -1" << std::endl;
    exit(1);
  }
}

It's the last function in App::Init and exits the application if some of the parameters have unacceptable values or are missing.