[Home]Boost.Build V2/UsingStandardGenerators

BOOST WIKI | Boost.Build V2 | RecentChanges | Preferences | Page List | Links List

A tutorial on how to use the standard generators of Boost.Build

In the following, we will start by getting a minimal project working with Boost.Build and use it to compile a simple C++ file. Next, we will preprocess the C++ file and then postprocess the resulting binary. Finally we will see that tasks that are not related to C/C++ can be executed in two different ways: by creating a generator or by using the make rule.

Getting our project up and running

We will start with an empty Jamfile and the following project-root.jam:

    import toolset : using ;
    using gcc ;

With this set up, you should be able to run bjam, even though it will not do anything.

Compiling a C++ file

Let's now create our first C++ file: hello_world.cpp
 #include <iostream>
 
 using std::cout;
 using std::endl;
 int main() {
     cout << "Hello, world!" << endl;
     return 0;
 }

The following Jamfile will take that and create an executable called hello

 exe hello : hello_world.cpp ;

If you run bjam now, you will find the newly create 'hello' executable in the bin/gcc/debug directory

Preprocessing a C++ file

Imagine that we could create our hello_world.cpp from a text file that contains the message to be printed, say hello_world.str

 Hello, world!

To include that step into our project we will have to inform Boost.build that there exists a type of file that we call MESSAGE, with extension .str.

    import type : register ;
    register MESSAGE : str ;

Next, we create a action that will convert MESSAGE into CPP. Here, we will do that using only UNIX shell commands:

 actions convert {
     printf "" > "$(<)"
     printf "#include <iostream>\n" >> "$(<)"
     printf "\n" >> "$(<)"
     printf "using std::cout;\n" >> "$(<)"
     printf "using std::endl;\n" >> "$(<)"
     printf "\n" >> "$(<)"
     printf "int main() {\n" >> "$(<)"
     printf "    cout << \"" >> "$(<)"
     awk '{printf $0}' "$(>)" >> "$(<)"
     printf "\" << endl;\n" >> "$(<)"
     printf "    return 0;\n" >> "$(<)"
     printf "}\n" >> "$(<)"
 }
 IMPORT $(__name__) : convert : : convert ;

Everything inside the actions statement is going to be executed by the shell and is therefore platform dependent. The MESSAGE file for the conversion is $(>), while the CPP file is $(<). The IMPORT statement make the actions visible to other parts of the system.

We now have to tell Boost.Build that convert can be use to generate CPP files:

    import generators : register-standard ;
    register-standard convert : MESSAGE : CPP ;

Having done all that, we can now create the hello executable from hello_word.str.

    exe hello : hello_world.str ;

Here's the complete Jamfile

 import type : register ;
 register MESSAGE : str ;
 
 actions convert {
     printf "" > "$(<)"
     printf "#include <iostream>\n" >> "$(<)"
     printf "\n" >> "$(<)"
     printf "using std::cout;\n" >> "$(<)"
     printf "using std::endl;\n" >> "$(<)"
     printf "\n" >> "$(<)"
     printf "int main() {\n" >> "$(<)"
     printf "    cout << \"" >> "$(<)"
     awk '{printf $0}' "$(>)" >> "$(<)"
     printf "\" << endl;\n" >> "$(<)"
     printf "    return 0;\n" >> "$(<)"
     printf "}\n" >> "$(<)"
 }
 IMPORT $(__name__) : convert : : convert ;
 
 import generators : register-standard ;
 register-standard convert : MESSAGE : CPP ;
 
 exe hello : hello_world.str ;

Postprocessing a EXE file (gzip)

A similar procedure can be applied to the resulting executable. Let's try and compress it.

First, we need to tell Boost.Build about this new type of files:

    import type : register ;
    register COMPRESSED : gz : : main ;
The last argument (main) will automatically create a rule that we can use to create compressed files, just as we used the exe rule above.

And again, we can define a simple set of actions that do the actual work:

    actions compress {
        gzip -c "$(>)" > "$(<)"
    }
    IMPORT $(__name__) : compress : : compress ;

Next step: register the conversion.

    import generators : register-standard ;
    register-standard compress : EXE : COMPRESSED ;

We are now ready to use the newly-created compressed rule

    compresssed hello : hello_world.str ;

The contents of our Jamfile so far are:

 import type : register ;
 register MESSAGE : str ;
 
 actions convert {
     printf "" > "$(<)"
     printf "#include <iostream>\n" >> "$(<)"
     printf "\n" >> "$(<)"
     printf "using std::cout;\n" >> "$(<)"
     printf "using std::endl;\n" >> "$(<)"
     printf "\n" >> "$(<)"
     printf "int main() {\n" >> "$(<)"
     printf "    cout << \"" >> "$(<)"
     awk '{printf $0}' "$(>)" >> "$(<)"
     printf "\" << endl;\n" >> "$(<)"
     printf "    return 0;\n" >> "$(<)"
     printf "}\n" >> "$(<)"
 }
 IMPORT $(__name__) : convert : : convert ;
 
 import generators : register-standard ;
 register-standard convert : MESSAGE : CPP ;
 
 import type : register ;
 register COMPRESSED : gz : : main ;
 
 actions compress {
     gzip -c "$(>)" > "$(<)"
 }
 IMPORT $(__name__) : compress : : compress ;
 
 import generators : register-standard ;
 register-standard compress : EXE : COMPRESSED ;
 
 compressed hello : hello_world.str ;

From start to end, all by ourselves

Let's say that we don't want to create an binary executable. Instead, let's create a shell script. The sequence of conversion that we want to achieve is
    .str -> .sh -> .sh.gz

While we implement those conversions, let's reorganize our code a little by introducing three more files.

In the sh.jam file we will register the new type with Boost.Build

    import type : register ;
    register SHELL : sh ;

The conversion from MESSAGE to SHELL will be represented in the file str2sh.jam

 import sh ;
 import type : register ;
 register MESSAGE : str ;
 
 actions convert {
     printf "" > "$(<)"
     printf "#!/bin/sh\n" >> "$(<)"
     printf "\n" >> "$(<)"
     printf "echo \"" >> "$(<)"
     awk '{printf $0}' "$(>)" >> "$(<)"
     printf "\"\n" >> "$(<)"
 }
 
 import generators : register-standard ;
 register-standard str2sh.convert : MESSAGE : SHELL ;

In the sh2zip.jam file we will describe the second conversion

 import sh ;
 import type : register ;
 register COMPRESSED : sh.gz : : main ;
 
 actions compress {
     gzip -c "$(>)" > "$(<)"
 }
 import generators : register-standard ;
 register-standard sh2zip.compress : SHELL : COMPRESSED ;

Our Jamfile now can simply contain

 import str2sh ;
 import sh2zip ;
 
 compressed hello : hello_world.str ;

Multi-platform support

Our code so far works well on most UNIX-like platforms, including cygwin. Even if we could assume gzip is available, it is unlikely to find awk and printf on a MS Windows operating system. Although we may not be able to run hello.sh on Windows, if we are building on that OS, we still want hello.sh to be generated.

To achieve that, we first need a replacement for the convert rule. We are a little limited by what the windows shell can do here but the following will do

    actions convert.NT {
        echo #!/bin/sh > "$(<).tmp"
        echo cat ^<^<EOF >> "$(<).tmp"
        copy "$(<).tmp" + "$(>)" "$(<)"
    }

We will now introduce a convert rule that will dispatch either to convert.NT or to convert.UNIX depending on the operating system.

    import os : name ;
    rule convert ( target : sources * : properties * )
    {
        switch [ os.name ]
        {
            case "NT" :
                convert.NT $(target) : $(sources) ;
            case "*" :
                convert.UNIX $(target) : $(sources) ;
        }
    }

Our str2sh.jam file looks like this:

 import sh ;
 import type : register ;
 register MESSAGE : str ;
 
 actions convert.UNIX {
     printf "" > "$(<)"
     printf "#!/bin/sh\n" >> "$(<)"
     printf "\n" >> "$(<)"
     printf "echo \"" >> "$(<)"
     awk '{printf $0}' "$(>)" >> "$(<)"
     printf "\"\n" >> "$(<)"
 }
 
 actions convert.NT {
     echo #!/bin/sh > "$(<).tmp"
     echo cat ^<^<EOF >> "$(<).tmp"
     copy "$(<).tmp" + "$(>)" "$(<)"
 }
 
 import os : name ;
 rule convert ( target : sources * : properties * )
 {
     switch [ os.name ]
     {
         case "NT" :
             convert.NT $(target) : $(sources) ;
         case "*" :
             convert.UNIX $(target) : $(sources) ;
     }
 }
 
 import generators : register-standard ;
 register-standard str2sh.convert : MESSAGE : SHELL ;

We may also want to replace gzip with zip on Windows. Besides creating a dispatching compress, we will need:

    import os : name ;
    switch [ os.name ]
    {
        case "NT" :
            register COMPRESSED : sh.zip : : main ;
        case "*" :
            register COMPRESSED : sh.gz : : main ;
    }

Here is sh2zip.jam file

 import sh ;
 import type : register ;
 
 import os : name ;
 switch [ os.name ]
 {
     case "NT" :
         register COMPRESSED : sh.zip : : main ;
     case "*" :
         register COMPRESSED : sh.gz : : main ;
 }
 
 actions compress.UNIX {
     gzip -c "$(>)" > "$(<)"
 }
 
 actions compress.NT {
     zip "$(<)" "$(>)"
 }
 
 import os : name ;
 rule compress ( target : sources * : properties * )
 {
     switch [ os.name ]
     {
         case "NT" :
             compress.NT $(target) : $(sources) ;
         case "*" :
             compress.UNIX $(target) : $(sources) ;
     }
 }
 
 import generators : register-standard ;
 register-standard sh2zip.compress : SHELL : COMPRESSED ;

Configuration of generators

Suppose now that we want to allow the users to specify the degree of compression when they call the compress rule. We would like them to be able to write
    compress hello : hello_world.str : <degree>maximum ;

The first thing we need is a new feature called degree that can take one of two values: minimum or maximum

    import feature : feature ;
    feature degree : minimum maximum : free ;

Now we map those values into options to be passed to gzip and zip

    import toolset : flags ;
    flags sh2zip.compress OPTIONS <degree>maximum : -9 ;
    flags sh2zip.compre

Finally, our compression tools can used the options:

    actions compress.UNIX {
        gzip $(OPTIONS) -c "$(>)" > "$(<)"
    }

    actions compress.NT {
        zip $(OPTIONS) "$(<)" "$(>)"
    }

Here's the resulting sh2zip.jam

 import sh ;
 import type : register ;
 import os : name ;
 switch [ os.name ]
 {
     case "NT" :
         register COMPRESSED : sh.zip : : main ;
     case "*" :
         register COMPRESSED : sh.gz : : main ;
 }
 
 import feature : feature ;
 feature compression : minimum maximum : free ;
 
 import toolset : flags ;
 flags sh2zip.compress OPTIONS <degree>maximum : -9 ;
 flags sh2zip.compress OPTIONS <degree>minimum : -1 ;
 
 actions compress.UNIX {
     gzip $(OPTIONS) -c "$(>)" > "$(<)"
 }
 
 actions compress.NT {
     zip $(OPTIONS) "$(<)" "$(>)"
 }
 
 import os : name ;
 rule compress ( target : sources * : properties * )
 {
     switch [ os.name ]
     {
         case "NT" :
             compress.NT $(target) : $(sources) ;
         case "*" :
             compress.UNIX $(target) : $(sources) ;
     }
 }
 
 import generators : register-standard ;
 register-standard sh2zip.compress : SHELL : COMPRESSED ;

The make rule

When only very simple conversions are involved, we can attach actions to targets and sources using the make rule. Here's a Jamfile that does that
 make hello.sh.gz : hello_world.str : create-and-compress ;
 actions create-and-compress {
     printf "" > "$(<:S=)"
     printf "#!/bin/sh\n" >> "$(<:S=)"
     printf "\n" >> "$(<:S=)"
     printf "echo \"" >> "$(<:S=)"
     awk '{printf $0}' "$(>)" >> "$(<:S=)"
     printf "\"\n" >> "$(<:S=)"
     gzip -c "$(<:S=)" > "$(<)"
 }
 IMPORT $(__name__) : create-and-compress : : create-and-compress ;

[buy lipitor online] [buy lipitor] [[buy lipitor online]]


BOOST WIKI | Boost.Build V2 | RecentChanges | Preferences | Page List | Links List
Edit text of this page | View other revisions
Last edited August 7, 2008 1:19 pm (diff)
Search:
Disclaimer: This site not officially maintained by Boost Developers