diff --git a/.gitignore b/.gitignore index a4dcafe51a..729f770ed8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ lib*.so lib*.so.* *.a -*/build +build .dep __pycache__ diff --git a/libuavcan/CMakeLists.txt b/libuavcan/CMakeLists.txt index b990b74eb9..81f1114c53 100644 --- a/libuavcan/CMakeLists.txt +++ b/libuavcan/CMakeLists.txt @@ -2,7 +2,7 @@ # Copyright (C) 2014 Pavel Kirienko # -cmake_minimum_required(VERSION 2.6) +cmake_minimum_required(VERSION 2.8) if(DEFINED CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE} CACHE STRING "Debug Release RelWithDebInfo MinSizeRel") @@ -31,9 +31,10 @@ endif () # DSDL compiler invocation # Probably output files should be saved into CMake output dir? # +execute_process(COMMAND ./setup.py build WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/dsdl_compiler) set(DSDLC_INPUTS "test/dsdl_test/root_ns_a" "test/dsdl_test/root_ns_b" "${CMAKE_SOURCE_DIR}/../dsdl/uavcan") set(DSDLC_OUTPUT "include/dsdlc_generated") -add_custom_target(dsdlc dsdl_compiler/dsdlc.py ${DSDLC_INPUTS} -O${DSDLC_OUTPUT} +add_custom_target(dsdlc dsdl_compiler/uavcan_dsdlc ${DSDLC_INPUTS} -O${DSDLC_OUTPUT} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) include_directories(${DSDLC_OUTPUT}) @@ -64,10 +65,12 @@ file(GLOB_RECURSE LIBUAVCAN_CXX_FILES RELATIVE ${CMAKE_SOURCE_DIR} "src/*.cpp") add_library(uavcan SHARED ${LIBUAVCAN_CXX_FILES}) add_dependencies(uavcan dsdlc) -install(TARGETS uavcan DESTINATION lib) -install(DIRECTORY include/uavcan DESTINATION include) -install(DIRECTORY include/dsdlc_generated/uavcan DESTINATION include) # Merging generated .hpp with lib's .hpp -install(DIRECTORY src/ DESTINATION src/uavcan) +install(TARGETS uavcan DESTINATION lib) +install(DIRECTORY include/uavcan DESTINATION include) +install(DIRECTORY include/dsdlc_generated/uavcan DESTINATION include) # Generated and lib's .hpp +install(DIRECTORY src DESTINATION src/uavcan) +install(DIRECTORY ../dsdl DESTINATION share/uavcan) # TODO: move to top-level CMake script +install(CODE "execute_process(COMMAND ./setup.py install WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/dsdl_compiler)") # # Tests and static analysis - only for debug builds @@ -91,8 +94,8 @@ function(add_libuavcan_test name library flags) # Adds GTest executable and crea # Tests run automatically upon successful build # If failing tests need to be investigated with debugger, use 'make --ignore-errors' add_custom_command(TARGET ${name} POST_BUILD - COMMAND ./${name} > "${name}.log" - WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + COMMAND ./${name} > "${name}.log" + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) endfunction() if (DEBUG_BUILD) @@ -109,9 +112,11 @@ if (DEBUG_BUILD) # Additional flavours of the library add_library(uavcan_cpp03 SHARED ${LIBUAVCAN_CXX_FILES}) set_target_properties(uavcan_cpp03 PROPERTIES COMPILE_FLAGS ${cpp03_flags}) + add_dependencies(uavcan_cpp03 dsdlc) add_library(uavcan_optim SHARED ${LIBUAVCAN_CXX_FILES}) set_target_properties(uavcan_optim PROPERTIES COMPILE_FLAGS ${optim_flags}) + add_dependencies(uavcan_optim dsdlc) # GTest executables find_package(GTest REQUIRED) diff --git a/libuavcan/dsdl_compiler/dsdlc.py b/libuavcan/dsdl_compiler/libuavcan_dsdl_compiler/__init__.py old mode 100755 new mode 100644 similarity index 73% rename from libuavcan/dsdl_compiler/dsdlc.py rename to libuavcan/dsdl_compiler/libuavcan_dsdl_compiler/__init__.py index 5f5361a4e7..10c28fd68c --- a/libuavcan/dsdl_compiler/dsdlc.py +++ b/libuavcan/dsdl_compiler/libuavcan_dsdl_compiler/__init__.py @@ -1,19 +1,11 @@ -#!/usr/bin/env python3 # # UAVCAN DSDL compiler for libuavcan # # Copyright (C) 2014 Pavel Kirienko # -import sys, os, argparse, logging, errno +import sys, os, logging, errno from mako.template import Template - -RUNNING_FROM_SRC_DIR = os.path.abspath(__file__).endswith(os.path.join('libuavcan', 'dsdl_compiler', 'dsdlc.py')) -if RUNNING_FROM_SRC_DIR: - print('Running from the source directory') - scriptdir = os.path.dirname(os.path.abspath(__file__)) - sys.path.append(os.path.join(scriptdir, '..', '..', 'pyuavcan')) - from pyuavcan import dsdl MAX_BITLEN_FOR_ENUM = 31 @@ -21,11 +13,27 @@ OUTPUT_FILE_EXTENSION = 'hpp' OUTPUT_FILE_PERMISSIONS = 0o444 # Read only for all TEMPLATE_FILENAME = os.path.join(os.path.dirname(__file__), 'data_type_template.tmpl') -# ----------------- +__all__ = ['run', 'logger', 'DsdlCompilerException'] class DsdlCompilerException(Exception): pass +logger = logging.getLogger(__name__) + +def run(source_dirs, include_dirs, output_dir): + assert isinstance(source_dirs, list) + assert isinstance(include_dirs, list) + assert isinstance(output_dir, str) + + types = run_parser(source_dirs, include_dirs + source_dirs) + if not types: + die('No type definitions were found') + + logger.info('%d types total', len(types)) + run_generator(types, output_dir) + +# ----------------- + def pretty_filename(filename): a = os.path.abspath(filename) r = os.path.relpath(filename) @@ -43,20 +51,14 @@ def makedirs(path): raise def die(text): - print(text, file=sys.stderr) - exit(1) - -def configure_logging(verbosity): - fmt = '%(message)s' - level = { 0: logging.WARNING, 1: logging.INFO, 2: logging.DEBUG }.get(verbosity or 0, logging.DEBUG) - logging.basicConfig(stream=sys.stderr, level=level, format=fmt) + raise DsdlCompilerException(str(text)) def run_parser(source_dirs, search_dirs): try: types = dsdl.parse_namespaces(source_dirs, search_dirs) except dsdl.DsdlException as ex: - logging.info('Parser failure', exc_info=True) - die(str(ex)) + logger.info('Parser failure', exc_info=True) + die(ex) return types def run_generator(types, dest_dir): @@ -64,13 +66,13 @@ def run_generator(types, dest_dir): dest_dir = os.path.abspath(dest_dir) # Removing '..' makedirs(dest_dir) for t in types: - logging.info('Generating type %s', t.full_name) + logger.info('Generating type %s', t.full_name) filename = os.path.join(dest_dir, type_output_filename(t)) text = generate_one_type(t) write_generated_data(filename, text) except Exception as ex: - logging.info('Generator failure', exc_info=True) - die(str(ex)) + logger.info('Generator failure', exc_info=True) + die(ex) def write_generated_data(filename, data): dirname = os.path.dirname(filename) @@ -81,12 +83,12 @@ def write_generated_data(filename, data): with open(filename) as f: existing_data = f.read() if data == existing_data: - logging.info('Up to date [%s]', pretty_filename(filename)) + logger.info('Up to date [%s]', pretty_filename(filename)) return - logging.info('Rewriting [%s]', pretty_filename(filename)) + logger.info('Rewriting [%s]', pretty_filename(filename)) os.remove(filename) else: - logging.info('Creating [%s]', pretty_filename(filename)) + logger.info('Creating [%s]', pretty_filename(filename)) # Full rewrite with open(filename, 'w') as f: @@ -94,7 +96,7 @@ def write_generated_data(filename, data): try: os.chmod(filename, OUTPUT_FILE_PERMISSIONS) except (OSError, IOError) as ex: - logging.warning('Failed to set permissions for %s: %s', pretty_filename(filename), ex) + logger.warning('Failed to set permissions for %s: %s', pretty_filename(filename), ex) def type_to_cpp_type(t): if t.category == t.CATEGORY_PRIMITIVE: @@ -192,26 +194,3 @@ def generate_one_type(t): text = text.replace('\n\n\n\n', '\n\n').replace('\n\n\n', '\n\n') text = text.replace('{\n\n ', '{\n ') return text - -# ----------------- - -DESCRIPTION = '''UAVCAN DSDL compiler. Takes an input directory that contains an hierarchy of DSDL -definitions and converts it into compatible hierarchy of C++ types for libuavcan.''' - -DEFAULT_OUTDIR = './dsdlc_generated' - -argparser = argparse.ArgumentParser(description=DESCRIPTION) -argparser.add_argument('source_dir', nargs='+', help='source directory with DSDL definitions') -argparser.add_argument('--verbose', '-v', action='count', help='verbosity level (-v, -vv)') -argparser.add_argument('--outdir', '-O', default=DEFAULT_OUTDIR, help='output directory, default %s' % DEFAULT_OUTDIR) -argparser.add_argument('--incdir', '-I', default=[], action='append', help='nested type namespaces') -args = argparser.parse_args() - -configure_logging(args.verbose) - -types = run_parser(args.source_dir, args.incdir + args.source_dir) -if not types: - die('No type definitions were found') -logging.info('%d types total', len(types)) - -run_generator(types, args.outdir) diff --git a/libuavcan/dsdl_compiler/data_type_template.tmpl b/libuavcan/dsdl_compiler/libuavcan_dsdl_compiler/data_type_template.tmpl similarity index 100% rename from libuavcan/dsdl_compiler/data_type_template.tmpl rename to libuavcan/dsdl_compiler/libuavcan_dsdl_compiler/data_type_template.tmpl diff --git a/libuavcan/dsdl_compiler/setup.py b/libuavcan/dsdl_compiler/setup.py new file mode 100755 index 0000000000..0b236890ab --- /dev/null +++ b/libuavcan/dsdl_compiler/setup.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 + +from distutils.core import setup + +args = dict( + name='uavcan_dsdlc', + version='0.1', + description='UAVCAN DSDL compiler for libuavcan', + packages=['libuavcan_dsdl_compiler'], + package_data={'libuavcan_dsdl_compiler': ['data_type_template.tmpl']}, + scripts=['uavcan_dsdlc'], + requires=['mako', 'pyuavcan'], + author='Pavel Kirienko', + author_email='pavel.kirienko@gmail.com', + url='http://uavcan.org', + license='MIT' +) + +setup(**args) diff --git a/libuavcan/dsdl_compiler/uavcan_dsdlc b/libuavcan/dsdl_compiler/uavcan_dsdlc new file mode 100755 index 0000000000..0aa6ec6216 --- /dev/null +++ b/libuavcan/dsdl_compiler/uavcan_dsdlc @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# +# UAVCAN DSDL compiler +# Probably it will be extended in future to add support for different programming languages. +# +# Copyright (C) 2014 Pavel Kirienko +# + +import os, sys, logging, argparse + +def configure_logging(verbosity): + fmt = '%(message)s' + level = { 0: logging.WARNING, 1: logging.INFO, 2: logging.DEBUG }.get(verbosity or 0, logging.DEBUG) + logging.basicConfig(stream=sys.stderr, level=level, format=fmt) + +def die(text): + print(text, file=sys.stderr) + exit(1) + +RUNNING_FROM_SRC_DIR = os.path.abspath(__file__).endswith(os.path.join('libuavcan', 'dsdl_compiler', 'uavcan_dsdlc')) +if RUNNING_FROM_SRC_DIR: + print('Running from the source directory') + scriptdir = os.path.dirname(os.path.abspath(__file__)) + sys.path.append(os.path.join(scriptdir, '..', '..', 'pyuavcan')) + sys.path.append(os.path.join(scriptdir)) + +DEFAULT_OUTDIR = './dsdlc_generated' + +DESCRIPTION = '''UAVCAN DSDL compiler. Takes an input directory that contains an hierarchy of DSDL +definitions and converts it into compatible hierarchy of C++ types for libuavcan.''' + +argparser = argparse.ArgumentParser(description=DESCRIPTION) +argparser.add_argument('source_dir', nargs='+', help='source directory with DSDL definitions') +argparser.add_argument('--verbose', '-v', action='count', help='verbosity level (-v, -vv)') +argparser.add_argument('--outdir', '-O', default=DEFAULT_OUTDIR, help='output directory, default %s' % DEFAULT_OUTDIR) +argparser.add_argument('--incdir', '-I', default=[], action='append', help='nested type namespaces') +args = argparser.parse_args() + +configure_logging(args.verbose) + +from libuavcan_dsdl_compiler import run as dsdlc_run +try: + # TODO: collect include dirs from environment variable UAVCAN_DSDL_INCLUDE + dsdlc_run(args.source_dir, args.incdir, args.outdir) +except Exception as ex: + logging.error('Compiler failure', exc_info=True) + die(str(ex))