#
#  Protobuf support for Machinekit
#
#  build Python modules, and C++ bindings from .proto files
#  build nanopb C bindings for kernel & userland RT components (and possibly embedded devices)
#
#  optionally build Javscript bindings if  --enable-proto-js is given to configure
#
#
#  The proto definitions are held in a separate repo: github.com/machinekit/machinetalk-protobuf.
#
#  To change the proto definitions:
#
#     1. create a .proto file under machinetalk/proto
#
#     2. add the message type to machinetalk/proto/types.proto
#        enum MessageType, e.g. MT_EMC_OPERATOR_TEXT_TYPE
#
#     3. if the message will NOT be used in-kernel/RT components, consider
#	 tagging it with the option  [(nanopb).type = FT_IGNORE];
# 	 this will reduce the overhead of RT protobuf support.
#
#     4. refer to the message type by including
#        machinetalk/generated/types.pb-c.h and use e.g.
#        MESSAGE_TYPE_MT_EMC_OPERATOR_TEXT_TYPE
#
#    When done, file a PR against github.com/machinekit/machinetalk-protobuf.
#
#
# TODO:
#    generate compiled Python bindings for better performance, current options
#    (sorted in decreasing order of estimated stability/completeness at this time):
#
#    The upcoming 2.6 protobuf release should support C compiled Python bindings out of the box.
#
# Michael Haberler 2/2013

NAMESPACEDIR := machinetalk/protobuf
MACHINETALK := machinetalk
MTINCLUDES := $(MACHINETALK)/include
PBGEN := $(MACHINETALK)/build
PROTOSRCDIR := $(MACHINETALK)/proto/src
PROTODIR := $(PROTOSRCDIR)/$(NAMESPACEDIR)
NANOPB := $(MACHINETALK)/nanopb
NPBPROTODIR :=  $(NANOPB)/generator/proto

# this is a hack to get runtests to pass
# build/header_sanity just blindly follows the breadcrumb trail for all
# includes within headers and will not look in the subdirs nicely created
# for some of those files, because the files that reference them, have
# them present in the PWD in the source tree or have an INCLUDE_PATH set.

../include/%.h: ./$(PBGEN)/$(NAMESPACEDIR)/%.h
	$(ECHO) Copying header file $@
	$(Q)cp  $^ $@

../include/%.h: ./$(NANOPB)/%.h
	$(ECHO) Copying header file $@
	$(Q)cp  $^ $@


# Javascript support
JSGEN=$(MACHINETALK)/tutorial/htdocs/js/machinetalk

# proto2js options

# find nanopb.proto:
PROTOBUFJS_OPT :=
# namespace
PROTOBUFJS_OPT += -class
# these alternativesI dont understand yet:
#PROTOBUFJS_OPT := -commonjs=pb
# http://en.wikipedia.org/wiki/Asynchronous_module_definition
#PROTOBUFJS_OPT := -amd

# protobuf namespace; all protos except nanopb.proto
JSNAMESPACE := =pb


# this comes in with the machinetalk-protobuf subtree merge:
GENDEP := $(MACHINETALK)/proto/scripts/protoc-gen-depends

# the directory where descriptor.proto lives:
GPBINCLUDE :=  $(shell $(PKG_CONFIG) --variable=includedir protobuf)
DESCDIR    :=  $(GPBINCLUDE)/google/protobuf

# search path for .proto files
# see note on PBDEP_OPT below
vpath %.proto  $(PROTODIR):$(GPBINCLUDE):$(DESCDIR)/compiler

# the set of all proto specs generated files depend on
PROTO_SPECS := $(wildcard $(PROTODIR)/*.proto)

USERSRCS += $(PROTO_SPECS)

# C bindings library
PROTOCLIB = ../lib/libmachinetalk-pb2.so

# C++ bindings library
PROTOCXXLIB = ../lib/libmachinetalk-pb2++.so

# Nanopb C bindings library
NANOPROTOCLIB := ../lib/libmachinetalk-npb.so
PROTOC_FLAGS :=
PROTOCXX_LDFLAGS := $(PROTOBUF_LIBS)
PROTOCXX_FLAGS :=
PROTOCXX_CXX_FLAGS := -fPIC $(PROTOBUF_CFLAGS)

# tell protobuf to use compiled C for Python bindings
# not available in 2.4.1/jessie
#export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=cpp

# NANOPB_CFLAGS are also passed to module build through
# RTFLAGS - see hal/msgcomponents/Submakefile
NANOPB_CFLAGS := -DPB_FIELD_32BIT '-DPB_SYSTEM_HEADER=<'$(MACHINETALK)'/include/pb-linuxcnc.h>'
NANOPB_OPTIONS := $(MACHINETALK)/nanopb.options

# any extra flags to C compile nanopb bindings
# this gets exported to RTFLAGS so dont add -fPIC here (stalls kthreads build)
# machinetalk/msgcomponents/Submakefile:RTFLAGS +=   $(PROTO_NANOPB_CFLAGS)
PROTO_NANOPB_CFLAGS := $(NANOPB_CFLAGS)

# nanopb library .h/.c files
NANOPB_INCS := $(wildcard $(NANOPB)/*.h)
NANOPB_C_SRCS := $(wildcard $(NANOPB)/*.c)

PROTO_NANOPB_C_INCS := $(subst $(PROTODIR)/, \
	$(PBGEN)/$(NAMESPACEDIR)/,  \
	$(patsubst %.proto, %.npb.h, $(PROTO_SPECS)))

PROTO_NANOPB_C_INCS += $(PBGEN)/$(NAMESPACEDIR)/nanopb.npb.h

PROTO_NANOPB_C_SRCS := $(subst $(PROTODIR)/, \
	$(PBGEN)/$(NAMESPACEDIR)/,  \
	$(patsubst %.proto, %.npb.c, $(PROTO_SPECS)))
PROTO_NANOPB_C_SRCS += $(PBGEN)/$(NAMESPACEDIR)/nanopb.npb.c

# Nanopb generator support
NANOPB_SUPPORT := ../lib/python/$(NAMESPACEDIR)/nanopb_pb2.py
NANOPB_SUPPORT += ../lib/python/$(NAMESPACEDIR)/nanopb_generator.py
NANOPB_SUPPORT += ../lib/python/$(NAMESPACEDIR)/protoc-gen-nanopb
NANOPB_SUPPORT += ../lib/python/$(NAMESPACEDIR)/__init__.py
NANOPB_SUPPORT += ../lib/python/$(MACHINETALK)/__init__.py

PROTO_PY_TARGETS := $(NANOPB_SUPPORT)

# machinetalk/proto/*.proto derived Python bindings
PROTO_PY_TARGETS += $(subst $(PROTODIR)/, \
	../lib/python/$(NAMESPACEDIR)/, \
	$(patsubst %.proto, %_pb2.py, $(PROTO_SPECS)))

PROTO_PY_TARGETS += ../lib/python/$(NAMESPACEDIR)/nanopb_pb2.py

# generated C++ includes
PROTO_CXX_INCS :=
PROTO_CXX_INCS += $(subst $(PROTODIR)/, \
	$(PBGEN)/$(NAMESPACEDIR)/,  \
	$(patsubst %.proto, %.pb.h, $(PROTO_SPECS)))
PROTO_CXX_INCS += $(PBGEN)/$(NAMESPACEDIR)/nanopb.pb.h

# generated C++ sources
PROTO_CXX_SRCS  :=  $(PBGEN)/$(NAMESPACEDIR)/nanopb.pb.cc
PROTO_CXX_SRCS  +=  $(subst $(PROTODIR)/, \
	$(PBGEN)/$(NAMESPACEDIR)/, \
	$(patsubst %.proto, %.pb.cc, $(PROTO_SPECS)))

# headers which are to go into ../include
MT_INSTALL_INCS := $(subst $(PBGEN), \
	../include,  \
	$(PROTO_CXX_INCS) $(PROTO_C_INCS) $(PROTO_NANOPB_C_INCS))

MT_INSTALL_INCS +=  \
	../include/machinetalk/nanopb/pb.h \
	../include/machinetalk/nanopb/pb_encode.h \
	../include/machinetalk/nanopb/pb_decode.h

# generate emctypes.proto from emc/nml_intf/emc.hh
#$(PBGEN)/emctypes.proto: emc/nml_intf/emc.hh $(MACHINETALK)/scripts/extract-nmltypes
#	$(ECHO) "extracting protobuf message types for NML from" $<
#	@mkdir -p $(PBGEN)
#	$(Q)$(MACHINETALK)/scripts/extract-nmltypes $< >$@

# generate mottypes.proto from emc/motion/motion.h
#$(PBGEN)/mottypes.proto: ../../include/motion.h $(MACHINETALK)/scripts/extract-mottypes
#	$(ECHO) "extracting protobuf message types for Motion from" $<
#	@mkdir -p $(PBGEN)
#	$(Q)$(MACHINETALK)/scripts/extract-mottypes $< >$@

#TARGETS += $(PBGEN)/emctypes.proto 
#$(PBGEN)/mottypes.proto


# ---- generate dependcy files for .proto files
#
# the list of .d dep files for .proto files:
PROTO_DEPS :=  $(patsubst %,objects/%,$(patsubst %.proto,%.d,$(PROTO_SPECS)))
#
# options to the dependency generator protoc plugin
PBDEP_OPT :=
#PBDEP_OPT += --debug
PBDEP_OPT += --cgen=$(PBGEN)
PBDEP_OPT += --pygen=lib/python/$(NAMESPACEDIR)
PBDEP_OPT += --jsgen=$(JSGEN)
# this path must match the vpath arrangement exactly or the deps will be wrong
# unfortunately there is no way to extract the proto path in the code
# generator plugin
PBDEP_OPT += --vpath=$(PROTOSRCDIR)
PBDEP_OPT += --vpath=$(GPBINCLUDE)
PBDEP_OPT += --vpath=$(DESCDIR)/compiler

#objects/$(PROTODIR)/%.d: $(PROTODIR)/%.proto
objects/$(PROTODIR)/%.d: %.proto
	$(ECHO) "protoc compile dependencies for $<"
	@mkdir -p objects/$(PROTODIR)/
	$(Q)$(PROTOC) \
	--plugin=protoc-gen-depends=$(GENDEP) \
	--proto_path=$(PROTOSRCDIR)/ \
	--proto_path=$(GPBINCLUDE) \
	--depends_out="$(PBDEP_OPT)":./objects/$(PROTOSRCDIR)/ \
	 $<



ifneq ($(shell diff -q $(BASEPWD)/$(PROTODIR)/nanopb.proto $(BASEPWD)/$(NPBPROTODIR)/nanopb.proto),)
    $(error $(PROTODIR)/nanopb.proto and  $(NPBPROTODIR)/nanopb.proto do not match; \
     the machinetalk/proto subtree might be out of sync)
endif

#---------- C++ rules -----------
#
# generate .cc/.h from proto files
# for command.proto, generated files are: command.pb.cc	command.pb.h
$(PBGEN)/$(NAMESPACEDIR)/%.pb.cc $(PBGEN)/$(NAMESPACEDIR)/%.pb.h: %.proto
	$(ECHO) "protoc compile C++ $<"
	@mkdir -p $(PBGEN)
	$(Q)$(PROTOC) $(PROTOCXX_FLAGS) \
	--proto_path=$(PROTOSRCDIR)/ \
	--proto_path=$(GPBINCLUDE)/ \
	--cpp_out=./$(PBGEN)/ $<

# ----------- Nanopb c/h rules
#
# Nanopb: generate *.npb.{c,h} from proto files
# these depend on npb options
$(PBGEN)/$(NAMESPACEDIR)/%.npb.c $(PBGEN)/$(NAMESPACEDIR)/%.npb.h: %.proto $(NANOPB_SUPPORT)
	$(ECHO) "protoc compile nanopb C $<"
	@mkdir -p $(PBGEN)/$(NAMESPACEDIR)
	$(Q)$(PROTOC) $(PROTOC_FLAGS) \
	--plugin=protoc-gen-nanopb=../lib/python/$(NAMESPACEDIR)/protoc-gen-nanopb \
	--nanopb_out="--generated-include-format='#include \""$(NAMESPACEDIR)"/%s\"' --extension=.npb --options-file=$(MACHINETALK)/nanopb.options":$(PBGEN) \
	--proto_path=$(PROTOSRCDIR)/ \
	--proto_path=$(GPBINCLUDE) \
	$<

# ------------- Python rules ------------
#
# this is for the stock protobuf Python bindings -
# adapt here if using one of the accelerated methods
#
# generate Python modules from proto files
../lib/python/$(NAMESPACEDIR)/%_pb2.py: %.proto
	$(ECHO) "protoc compile python $<"
	@mkdir -p ../lib/python/$(NAMESPACEDIR)
	$(Q)$(PROTOC) $(PROTOC_FLAGS) \
	--proto_path=$(PROTOSRCDIR)/ \
	--proto_path=$(GPBINCLUDE)/ \
	--python_out=../lib/python/ $<


# ------------- ProtoBuf.js rules ------------
#
# see https://github.com/dcodeIO/ProtoBuf.js
#
# generate Javascript modules from proto files
$(JSGEN)/%.js: %.proto
	$(ECHO) $(PROTO2JS)" compile js $<"
	@mkdir -p $(JSGEN)
	$(Q)$(PROTO2JS) 	$< \
	$(PROTOBUFJS_OPT)$(JSNAMESPACE) \
	> $@

# generated Javasript sources
# everything is namespace pb except nanopb.proto
PROTO_JS_SRCS  :=  $(subst $(PROTODIR)/, \
	$(JSGEN)/, \
	$(filter-out $(PROTODIR)/nanopb.js, $(patsubst %.proto, %.js, $(PROTO_SPECS))))
PROTO_JS_SRCS +=$(JSGEN)/nanopb.js



# --- misc supporting files ----
../lib/python/$(NAMESPACEDIR)/nanopb_generator.py: $(NANOPB)/generator/nanopb_generator.py
	@mkdir -p ../lib/python/$(NAMESPACEDIR)/
	cp $^ $@

../lib/python/$(NAMESPACEDIR)/__init__.py:
	@mkdir -p ../lib/python/$(NAMESPACEDIR)/
	touch ../lib/python/$(NAMESPACEDIR)/__init__.py

../lib/python/$(MACHINETALK)/__init__.py:
	@mkdir -p ../lib/python/$(MACHINETALK)/
	touch ../lib/python/$(MACHINETALK)/__init__.py

# the nanopb plugin executable
../lib/python/$(NAMESPACEDIR)/protoc-gen-nanopb: $(NANOPB)/generator/protoc-gen-nanopb
	@mkdir -p ../lib/python/$(NAMESPACEDIR)/
	cp $^ $@
	chmod 755 $@

# ---------- libraries ---------
#
# Nanopb C bindings library
#
NANOPB_LIBSRCS = $(NANOPB_C_SRCS) $(PROTO_NANOPB_C_SRCS)
$(call TOOBJSDEPS, $(NANOPB_LIBSRCS)) : EXTRAFLAGS += $(PROTO_NANOPB_CFLAGS) -fPIC

$(NANOPROTOCLIB).0: $(patsubst %.c,objects/%.o,$(NANOPB_LIBSRCS))
	$(ECHO) Linking $(notdir $@)
	@mkdir -p ../lib
	@rm -f $@
	$(Q)$(CC) $(LDFLAGS) -Wl,-soname,$(notdir $@) -shared -o $@ $^


# C++ bindings library
$(call TOOBJSDEPS, $(PROTO_CXX_SRCS)) : EXTRAFLAGS += $(PROTOCXX_CXX_FLAGS)

$(PROTOCXXLIB).0: $(patsubst %.cc,objects/%.o,$(PROTO_CXX_SRCS))
	$(ECHO) Linking $(notdir $@)
	@mkdir -p ../lib
	@rm -f $@
	$(Q)$(CXX) $(LDFLAGS) -Wl,-soname,$(notdir $@) -shared -o $@ $^ \
		$(PROTOBUF_LIBS)

# protoc generated headers are exported to ../include
../include/%.h: ./$(NAMESPACEDIR)/$(PBGEN)/%.h
	$(ECHO) Copying header file $@
	$(Q)cp $^ $@

../include/%.hh: ./$(NAMESPACEDIR)/$(PBGEN)/%.hh
	$(ECHO) Copying header file $@
	$(Q)cp $^ $@

# This path is backwards isnt it?
# protoc generated headers are exported to ../include
../include/$(NAMESPACEDIR)/%.h: ./$(PBGEN)/$(NAMESPACEDIR)/%.h
	$(ECHO) Copying header file $@
	@mkdir -p $(dir $@)
	$(Q)cp $^ $@

../include/$(NAMESPACEDIR)/%.hh: ./$(PBGEN)/$(NAMESPACEDIR)/%.hh
	$(ECHO) Copying header file $@
	@mkdir -p $(dir $@)
	$(Q)cp $^ $@

# as are the nanop library headers
../include/$(MACHINETALK)/%.h: ./$(NANOPB)/%.h
	$(ECHO) Copying header file $@
	@mkdir -p $(dir $@)
	$(Q)cp $^ $@

../include/%.h: ./%.h
	$(ECHO) Copying header file $@
	@mkdir -p $(dir $@)
	$(Q)cp $^ $@

../include/%.h: ./$(MACHINETALK)/include/%.h
	$(ECHO) Copying header file $@
	@mkdir -p $(dir $@)
	$(Q)cp $^ $@

../include/%.hh: ./$(MACHINETALK)/include/%.hh
	$(ECHO) Copying header file $@
	@mkdir -p $(dir $@)
	$(Q)cp $^ $@

# those will be compiled
USERSRCS +=  $(NANOPB_C_SRCS) \
	$(PROTO_CXX_SRCS) \
	$(PROTO_NANOPB_C_SRCS)


GENERATED += $(PROTO_PY_TARGETS) \
	$(PBGEN)/$(NAMESPACEDIR)/nanopb.pb.h	\
	$(PBGEN)/$(NAMESPACEDIR)/nanopb.pb.cc \
	$(PROTO_CXX_SRCS) $(PROTO_NANOPB_C_SRCS) \
	$(PROTO_CXX_INCS) $(PROTO_NANOPB_C_INCS)

ifeq ($(BUILD_PROTO_JS),yes)
USERSRCS += $(PROTO_JS_SRCS) $(JSGEN)/nanopb.js
TARGETS += $(PROTO_JS_SRCS) $(JSGEN)/nanopb.js
endif


PYTARGETS += $(PROTO_PY_TARGETS)

INCLUDES += $(PBGEN)
INCLUDES += $(NANOPB)
INCLUDES += $(MTINCLUDES)

TARGETS += $(PROTO_CXX_INCS) $(PROTO_C_INCS) $(PROTO_NANOPB_C_INCS)
TARGETS += $(MT_INSTALL_INCS)
TARGETS += $(NANOPROTOCLIB) $(NANOPROTOCLIB).0 \
	$(PROTOCXXLIB) $(PROTOCXXLIB).0

clean: machinetalk-clean
machinetalk-clean:
	rm -rf $(PBGEN) ../include/$(MACHINETALK)
	rm -rf ../lib/python/$(MACHINETALK)
