Saturday, January 21, 2012

Boost.build bjam file to support Google protobuf compilation

This source code was adapted from Rodrigo Pinho Pereira de Souza ( pinhopro at gmail dot com )'s code and is now published under Boost License (as it was).

Usage:

1. Save the following source code into a file pb.jam;
2. Modify boostlib/tools/build/v2/user-config.jam:
and make the following changes;
using pb : /usr/local ;  # /usr/local is the prefix pointing to the Google protobuf installation;
3. In your project jam file

exe myexe : 
   message.proto 
   a.cpp b.cpp

    ; 

or
lib msg1 :   message.proto  ;
 exe myexe : 
   msg1
   a.cpp b.cpp
   : msg1 ;


Finally the source for pb.jam
# Copyright 2012 Liping Zhang (zhanglpg at gmail dot com)
# Copyright 2010 Rodrigo Pinho Pereira de Souza ( pinhopro at gmail dot com )
#
# Distributed under the Boost Software License, Version 1.0. (See
# accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)

import type ;
import generators ;
import feature ;
import common ;
import "class" : new ;

import modules ;
import feature ;
import project ;
import toolset : flags ;

project.initialize $(__name__) ;
project pb ;


type.register PROTO : proto ;

.project = [ project.current ] ;

# Helper utils for easy debug output
if [ MATCH (--debug-configuration) : [ modules.peek : ARGV ] ]
{
  .debug-configuration = TRUE ;
}

local rule debug-message ( message * )
{
  if $(.debug-configuration) = TRUE
  {
    ECHO notice: [protobuf-cfg] $(message) ;
  }
}


rule init ( prefix : condition * )
{
  project.push-current $(.project) ;

  .prefix = $(prefix) ;

  .incprefix = $(prefix)/include ;
  .libprefix = $(prefix)/lib ;
  .binprefix = $(prefix)/bin ;   

  debug-message  "rule init (" $(prefix) "," $(condition) ")" ;
  debug-message "include path: " $(.incprefix) ;
  debug-message "library path: " $(.libprefix) ;
  debug-message "bin path" $(.binprefix) ;
  
  if ! $(.initialized)
  {
    .initialized = true ;
    debug-message "initializing protobuf..." ;

    debug-message "registering proto type " ;
    #type.register PROTO : proto ;

    debug-message "registering protoc compiler " ;
    generators.register [ new proto-generator pb.protoc : PROTO : CPP(%.pb) H(%.pb) ] ;

    .PREFIX = $(prefix) ;
    debug-message "protobuf initialized." ;
  }

  debug-message "setup path for protoc tool" ;
  toolset.flags pb.protoc .BINPREFIX : $(.binprefix) ;

  local usage-requirements =
      $(.incprefix)
      $(.libprefix)
      $(.libprefix)
    ;
  debug-message "usage-requirements: " $(usage-requirements) ;

  local target-requirements = $(condition) ;

  lib protobuflib : 
    : # requirements 
      protobuf
       $(target-requirements)
    : # default-build
    : # usage-requirements
      $(usage-requirements)
    ;

  lib protobuf-litelib : 
    : # requirements 
      protobuf-lite
       $(target-requirements)
    : # default-build
    : # usage-requirements
      $(usage-requirements)
    ;

  project.pop-current ;

  debug-message "module pb initialized" ;
}


rule initialized ( )
{
  return $(.initialized) ;
}

class proto-generator : generator 
{
    import "class" : new ;

    rule __init__ ( * : * )
    {
        generator.__init__ $(1) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ;
    }

    rule run ( project name ? : property-set : sources * )
    {
        if ! $(sources[2])
        {
            # Accept only single source.
            local t = [ $(sources[1]).type ] ;
   
            if $(t) = PROTO
            {
                # The type is correct.

                # If no output name is specified, guess it from sources.
                if ! $(name)
                {
                    name = [ generator.determine-output-name $(sources) ] ;
     name = $(name)".pb" ;
                }

       

    local a = [ new action $(sources[1]) : pb.protoc : $(property-set) ] ;
 # since the default CPP suffix is .cpp and protoc generates .cc files, we need to specify the exact file name for CPP
           local target1 = [ new file-target $(name).cc exact : CPP : $(project) : $(a) ] ;

    local target2 = [ new file-target $(name) : H : $(project) : $(a) ] ;
    
    
    DEPENDS all : [ $(target1).actualize ] ;
    DEPENDS all : [ $(target2).actualize ] ;

    return [ virtual-target.register $(target1) ]
     [ virtual-target.register $(target2) ]
     ;
   }
  }
    }
}

#generators.register [ new proto-generator pb.protoc : PROTO : CPP H ] ;

actions protoc
{
  cp $(>[1]) . 
  $(.BINPREFIX[-1])/protoc $(>[1]:B)$(>[1]:S)  --cpp_out=$(<[1]:D) 
  
}

2 comments:

  1. I suggest the following amendment to the action, which removes the need for a copy and also works if you specify a separate output directory:

    actions protoc
    {
    $(.BINPREFIX[-1])/protoc --proto_path=$(>[1]:P) --cpp_out=$(<[1]:D) $(>)
    }

    ReplyDelete
  2. Thanks for the code! I am however getting an error "Duplicate name of actual target" when using the second variant of step 3. (first variant works fine). Hope you could help me further. Here is the the complete description of the error:

    error: Duplicate name of actual target: feature_vector.pb.cc
    error: previous virtual target { pb%pb.protoc-feature_vector.pb.cc.CPP { feature_vector.proto.PROTO } }
    error: created from models/msg1
    error: another virtual target { pb%pb.protoc-feature_vector.pb.cc.CPP { feature_vector.proto.PROTO } }
    error: created from
    error: added properties: none
    error: removed properties: none

    ReplyDelete