Using Protocol Buffers, Python and Mako for code generation.

Working on the SemaConnect EV charging station firmware over the past 8-9 years, I’ve written a lot of C code.   While I like C, I do not like writing boilerplate code. It is time-consuming and error-prone.  So I automate.

One strategy that I’ve adopted is using the Protocol Buffer language to specify interfaces, and transforming these into code using the python protobuf extension and  the mako template language.

#!/usr/bin/env python
import sys
import os
import types
from mako.template import Template
from mako.lookup import TemplateLookup
from google.protobuf.descriptor import ServiceDescriptor, EnumDescriptor, FieldDescriptor

def flatten( l ):
return [item for sublist in l for item in sublist]

sys.path.append( os.path.dirname(sys.argv[1]))
pbmod = __import__( os.path.basename(sys.argv[1]).split('.')[0])

# extract services, enums
services=[]
enums=[]
for name, desc in pbmod.__dict__.items():
if type(desc)==ServiceDescriptor:
services.append( desc )
elif str(type(desc)).find('EnumDescriptor')>=0:
enums.append( desc )
elif type(desc)==types.ModuleType and name.endswith('pb2'):
pass

# extract messages
messages = pbmod.DESCRIPTOR.message_types_by_name.values()

# sort message types so that if a depends on b,
# b appears before a in the list
def messages_sorted_by_dependency( msgs ):
sorted_msgs = [msg for msg in msgs]
for i in xrange(len(sorted_msgs)):
for j in xrange(len(sorted_msgs)):
for k in xrange(j, len(sorted_msgs)):
for field in sorted_msgs[j].fields:
if (field.type == FieldDescriptor.TYPE_MESSAGE and
field.message_type.name == sorted_msgs[k].name ):
# swap if a field in j depends on k
tmp = sorted_msgs[j]
sorted_msgs[j] = sorted_msgs[k]
sorted_msgs[k] = tmp
break

return sorted_msgs

messages = messages_sorted_by_dependency( pbmod.DESCRIPTOR.message_types_by_name.values() )

imports=set()
for message in messages:
for field in message.fields:
if field.type==FieldDescriptor.TYPE_MESSAGE:
fpkg = field.message_type.file
if fpkg != pbmod.DESCRIPTOR:
imports.add( fpkg )

outfname = sys.argv[3]
tmpl_fname = sys.argv[2]

mylookup = TemplateLookup(directories=['/'])
tmpl = Template(filename=tmpl_fname, lookup=mylookup)
output = tmpl.render( pbmod=pbmod, descriptor=pbmod.DESCRIPTOR, messages=messages, services=services, enums=enums, imports=imports)
open(outfname,"w").write( output )
print("Wrote " + outfname)
<% # some definitions package = descriptor.package PACKAGE = package.upper() # def fullname(T): return T.full_name.replace('.','_') %>
#ifndef ${PACKAGE}_H
#define ${PACKAGE}_H

%for enum in enums:                                                                                                                                                                                                              
enum ${fullname(enum)}                                                                                                                                                                                                    
{                                                                                                                                                                                                                                
%for enum_value in enum.values:                                                                                                                                                                                                  
    ${package}_${enum_value.name} = ${enum_value.number},                                                                                                                                                                        
%endfor                                                                                                                                                                                                                          
};                                                                                                                                                                                                                               
#define ${fullname(enum)}_MIN ${min(map(lambda x: x.number, enum.values))}                                                                                                                                                       
#define ${fullname(enum)}_MAX ${max(map(lambda x: x.number, enum.values))}                                                                                                                                                       
%endfor  
// END enum types

#endif // ${PACKAGE}_H

Leave a Reply

Your email address will not be published. Required fields are marked *