diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..ae35f656 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,68 @@ +--- +version: 2 +jobs: + build-focal: + docker: + - image: circleci/buildpack-deps:focal-scm + steps: + - checkout + - run: sudo apt install build-essential cmake libcppunit-dev doxygen graphviz + - run: git clone https://github.com/google/googletest + - run: mkdir googletest/build; cd googletest/build; cmake .. + - run: cd googletest/build; make; sudo make install + - run: mkdir build + - run: cd build && cmake -DDISABLE_CATKIN=ON .. + - run: cd build && make && make test + build-macos: + macos: + xcode: "10.0.0" + steps: + - checkout + - restore-cache: + keys: + - 2020-09-29-v1 + - run: brew update + - run: brew upgrade git + - run: > + for p in llvm cmake cppunit doxygen; do + brew list --version $p || brew install $p; + brew link $p; + done + - run: > + brew list --version graphviz + || brew install graphviz || brew install graphviz || :; + brew link graphviz + - run: git clone https://github.com/google/googletest + - run: mkdir googletest/build; cd googletest/build; cmake .. + - run: cd googletest/build; make; sudo make install + - save-cache: + key: 2020-09-29-v1 + paths: + - /usr/local/Homebrew + - /usr/local/Cellar + - run: mkdir build + - run: > + cd build && cmake + -DDISABLE_CATKIN=ON + -DENABLE_CATKIN_TESTING=ON + .. + - run: cd build && make + - run: cd build && ARGS="--verbose" make test + + +workflows: + version: 2 + build_all: + jobs: + - build-focal: + filters: + branches: + only: + - master + - build + - build-macos: + filters: + branches: + only: + - master + - build diff --git a/.gitignore b/.gitignore index 2634447d..652da978 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -.* .DS_Store *.coverage *.egg-info @@ -32,3 +31,6 @@ doc/html ipch Debug Release + +# Generated from package.xml.in, must live in top dir. +package.xml diff --git a/.travis.yml b/.travis.yml index d626c6e8..8b0d8017 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,52 @@ -os: - - linux - - osx +--- language: cpp -install: - - make install_deps - - source setup.bash -script: - - mkdir build && cd build && cmake .. -DPYTHON_EXECUTABLE=$(which python2) && make && make tests && make run_tests - - catkin_test_results . +matrix: + include: + - os: linux + dist: focal + compiler: gcc + script: + - sudo apt update + - > + sudo apt install + build-essential cmake doxygen graphviz googletest libgtest-dev + - mkdir build && cd build + - cmake -DDISABLE_CATKIN=ON .. + - make + - make test + - os: linux + dist: xenial + compiler: gcc + script: + - sudo apt update + - > + sudo apt install + build-essential catkin cmake doxygen graphviz libgtest-dev + - mkdir build && cd build + - cmake -DDISABLE_CATKIN=OFF -DCATKIN_ENABLE_TESTING=OFF .. + - make + + +# Does not build, breaks in make install_deps. +# - os: linux +# dist: precise +# compiler: gcc +# install: +# - make install_deps +# - source setup.bash +# script: +# - mkdir build && cd build +# - cmake -DPYTHON_EXECUTABLE=$(which python2) .. +# - make +# - make test + +# - os: osx +# compiler: clang +# install: +# - make install_deps +# - source setup.bash +# script: +# - mkdir build && cd build +# - cmake -DPYTHON_EXECUTABLE=$(which python2) .. +# - make +# - make run_tests diff --git a/CMakeLists.txt b/CMakeLists.txt index 49270209..c0d086e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,81 +1,150 @@ -cmake_minimum_required(VERSION 2.8.3) -project(serial) +cmake_minimum_required(VERSION 3.5.0) -# Find catkin -find_package(catkin REQUIRED) +# Public options and command line configuration +option(ENABLE_TEST_PROGRAM "Build test program" OFF) +option(CATKIN_ENABLE_TESTING "Enable catkin unit tests" ON) -if(APPLE) - find_library(IOKIT_LIBRARY IOKit) - find_library(FOUNDATION_LIBRARY Foundation) -endif() -if(UNIX AND NOT APPLE) - # If Linux, add rt and pthread - set(rt_LIBRARIES rt) - set(pthread_LIBRARIES pthread) - catkin_package( - LIBRARIES ${PROJECT_NAME} - INCLUDE_DIRS include - DEPENDS rt pthread - ) -else() - # Otherwise normal call +option(USE_CXX_SERIAL "build package name cxx-serial" OFF) +if (USE_CXX_SERIAL) + set(PKG_NAME cxx-serial) +else () + set(PKG_NAME serial) +endif () +message(STATUS "Building package ${PKG_NAME}") + +set(SERIAL_DOCDIR ${CMAKE_INSTALL_PREFIX}/share/doc/${PKG_NAME} + CACHE STRING "Installation root for doxygen docs." +) +option(DISABLE_CATKIN "Disable build of catkin package and tests" OFF) +if (DISABLE_CATKIN AND "${CATKIN_ENABLE_TESTING}" STREQUAL "" ) + set(CATKIN_ENABLE_TESTING OFF) +endif () + +set(PROJ_SOVERSION 1) +project(${PKG_NAME} + VERSION 1.2.1 + DESCRIPTION "Cross-platform, Serial Port library written in C++" + HOMEPAGE_URL "http://wjwwood.io/serial/" +) +include(GNUInstallDirs) +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) + +# Locate packages, headers and libraries +if (NOT DISABLE_CATKIN) + find_package(catkin REQUIRED) +endif () + +find_path(HAVE_STDINT_H NAMES stdint.h) + +if (NOT DISABLE_CATKIN) + # Build the catkin library + find_library(PTHREAD_LIB NAMES pthread REQUIRED) + if (PTHREAD_LIB) + set(PTHREAD_LIBRARIES ${PTHREAD_LIB}) + endif () + find_package(Rt) + if (RT_FOUND) + set(_RT RT) + endif () + configure_file(package.xml.in ${PROJECT_SOURCE_DIR}/package.xml @ONLY) catkin_package( LIBRARIES ${PROJECT_NAME} INCLUDE_DIRS include + DEPENDS ${_RT} PTHREAD ) -endif() + set(CMAKE_INSTALL_LIBDIR ${CATKIN_PACKAGE_LIB_DESTINATION}) + set(CMAKE_INSTALL_BINDIR ${CATKIN_GLOBAL_BIN_DESTINATION}) + set(CMAKE_INSTALL_INCLUDEDIR ${CATKIN_GLOBAL_INCLUDE_DESTINATION}) +endif () ## Sources -set(serial_SRCS - src/serial.cc - include/serial/serial.h - include/serial/v8stdint.h -) +set(serial_SRCS src/serial.cc include/serial/serial.h) +if (NOT HAVE_STDINT_H) + list(APPEND serial_SRCS include/serial/v8stdint.h) +endif () + if(APPLE) - # If OSX list(APPEND serial_SRCS src/impl/unix.cc) list(APPEND serial_SRCS src/impl/list_ports/list_ports_osx.cc) elseif(UNIX) - # If unix + # linux list(APPEND serial_SRCS src/impl/unix.cc) list(APPEND serial_SRCS src/impl/list_ports/list_ports_linux.cc) else() - # If windows + # win32 list(APPEND serial_SRCS src/impl/win.cc) list(APPEND serial_SRCS src/impl/list_ports/list_ports_win.cc) endif() -## Add serial library +set(serial_HEADERS include/serial/serial.h) +if (NOT HAVE_STDINT_H) + list(APPEND serial_HEADERS include/serial/v8stdint.h) +endif () + +# Build and link main library add_library(${PROJECT_NAME} ${serial_SRCS}) -if(APPLE) - target_link_libraries(${PROJECT_NAME} ${FOUNDATION_LIBRARY} ${IOKIT_LIBRARY}) -elseif(UNIX) - target_link_libraries(${PROJECT_NAME} rt pthread) -else() - target_link_libraries(${PROJECT_NAME} setupapi) -endif() +set_target_properties(${PROJECT_NAME} PROPERTIES + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJ_SOVERSION} + PUBLIC_HEADER "${serial_HEADERS}" +) +target_include_directories(${PROJECT_NAME} PUBLIC include) +if (HAVE_STDINT_H) + target_compile_definitions(${PROJECT_NAME} PRIVATE -DHAVE_STDINT_H) +endif () -## Uncomment for example -add_executable(serial_example examples/serial_example.cc) -add_dependencies(serial_example ${PROJECT_NAME}) -target_link_libraries(serial_example ${PROJECT_NAME}) +if (APPLE) + find_library(IOKIT_LIB IOKit) + find_library(FOUNDATION_LIB Foundation) + target_link_libraries(${PROJECT_NAME} ${FOUNDATION_LIB} ${IOKIT_LIB}) +elseif (UNIX) + target_link_libraries(${PROJECT_NAME} ${RT_LIBRARIES} ${PTHREAD_LIBRARIES}) +else () + target_link_libraries(${PROJECT_NAME} setupapi) +endif () -## Include headers -include_directories(include) -## Install executable +## Install main library, possibly the catkin one. install(TARGETS ${PROJECT_NAME} - ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} - LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} - RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME} ) -## Install headers -install(FILES include/serial/serial.h include/serial/v8stdint.h - DESTINATION ${CATKIN_GLOBAL_INCLUDE_DESTINATION}/serial) - -## Tests -if(CATKIN_ENABLE_TESTING) +# Other targets: test program, pkg-config and tests. +if (CATKIN_ENABLE_TESTING) + include(CTest) + find_package(GTest REQUIRED) + enable_testing() add_subdirectory(tests) endif() + +if (DISABLE_CATKIN) + configure_file(serial.pc.in ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.pc @ONLY) + install( + FILES ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.pc + DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig + ) +endif() + +if (ENABLE_TEST_PROGRAM) + add_executable(serial_example examples/serial_example.cc) + add_dependencies(serial_example ${PROJECT_NAME}) + target_link_libraries(serial_example ${PROJECT_NAME}) +endif() + +find_package(Doxygen) +if (DOXYGEN_FOUND AND DOXYGEN_DOT_FOUND) + set(DOXYGEN_OUT ${CMAKE_CURRENT_SOURCE_DIR}/doc/Doxyfile) + add_custom_target(doc ALL + COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generating API documentation with Doxygen" + VERBATIM + ) + install(DIRECTORY ${CMAKE_BINARY_DIR}/doc/html + DESTINATION ${SERIAL_DOCDIR} + ) +endif () diff --git a/FindRt.cmake b/FindRt.cmake new file mode 100644 index 00000000..05a6ed4d --- /dev/null +++ b/FindRt.cmake @@ -0,0 +1,19 @@ +# Try to find real time libraries +# Once done, this will define +# +# RT_FOUND - system has rt library +# RT_LIBRARIES - rt libraries directory + +if(RT_LIBRARIES) + set(RT_FIND_QUIETLY TRUE) +endif(RT_LIBRARIES) + +find_library(RT_LIBRARY rt) +set(RT_LIBRARIES ${RT_LIBRARY}) + +# handle the QUIETLY and REQUIRED arguments and set +# RT_FOUND to TRUE if all listed variables are TRUE +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Rt DEFAULT_MSG RT_LIBRARY) + +mark_as_advanced(RT_LIBRARY) diff --git a/README.md b/README.md index 3f99507a..05ccdc4b 100644 --- a/README.md +++ b/README.md @@ -16,15 +16,18 @@ API Documentation: http://wjwwood.github.com/serial/doc/1.1.0/index.html ### Dependencies -Required: -* [catkin](http://www.ros.org/wiki/catkin) - cmake and Python based buildsystem +Always required: * [cmake](http://www.cmake.org) - buildsystem + +Required when building the catkin package: +* [catkin](http://www.ros.org/wiki/catkin) - cmake and Python based buildsystem * [Python](http://www.python.org) - scripting language - * [empy](http://www.alcyone.com/pyos/empy/) - Python templating library - * [catkin_pkg](http://pypi.python.org/pypi/catkin_pkg/) - Runtime Python library for catkin +* [empy](http://www.alcyone.com/pyos/empy/) - Python templating library +* [catkin_pkg](http://pypi.python.org/pypi/catkin_pkg/) - Runtime Python library for catkin Optional (for tests): * [Boost](http://www.boost.org/) - Boost C++ librairies +* [Googletest](https://github.com/google/googletest) - Test framework Optional (for documentation): * [Doxygen](http://www.doxygen.org/) - Documentation generation tool diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000..a76a11f2 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,27 @@ +clone_folder: c:\project\serial +shallow_clone: false +clone_depth: 10 + +image: +- Visual Studio 2017 + +platform: +- Win32 + +configuration: RelWithDebInfo +test: OFF + +install: + - call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars32.bat" + - choco install cmake + +before_build: + - cd c:\project\serial + - mkdir build + - cd build + - cmake -DDISABLE_CATKIN=ON -DCATKIN_ENABLE_TESTING=OFF .. + +build_script: + - cmake build . + - cmake --build . --target install + diff --git a/include/serial/serial.h b/include/serial/serial.h index a1657852..323cb489 100644 --- a/include/serial/serial.h +++ b/include/serial/serial.h @@ -43,7 +43,12 @@ #include #include #include + +#ifdef HAVE_STDINT_H +#include +#else #include +#endif #define THROW(exceptionClass, message) throw exceptionClass(__FILE__, \ __LINE__, (message) ) diff --git a/package.xml b/package.xml.in similarity index 92% rename from package.xml rename to package.xml.in index 27781e14..2d56d828 100644 --- a/package.xml +++ b/package.xml.in @@ -1,7 +1,7 @@ - serial - 1.2.1 + @PROJECT_NAME@ + @PROJECT_VERSION@ Serial is a cross-platform, simple to use library for using serial ports on computers. This library provides a C++, object oriented interface for interacting with RS-232 diff --git a/serial.pc.in b/serial.pc.in new file mode 100644 index 00000000..ca6f25fc --- /dev/null +++ b/serial.pc.in @@ -0,0 +1,12 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=@CMAKE_INSTALL_PREFIX@ +libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@ +includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ + +Name: @PROJECT_NAME@ +Description: C++ portable serial ports communication library +Version: @PROJECT_VERSION@ + +Requires: +Libs: -L${libdir} -l@PROJECT_NAME@ +Cflags: -I${includedir} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e52a4d31..187f2549 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,4 +1,4 @@ -if(UNIX) +if (UNIX AND NOT DISABLE_CATKIN) catkin_add_gtest(${PROJECT_NAME}-test unix_serial_tests.cc) target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME} ${Boost_LIBRARIES}) if(NOT APPLE) @@ -6,7 +6,38 @@ if(UNIX) endif() if(NOT APPLE) # these tests are unreliable on macOS - catkin_add_gtest(${PROJECT_NAME}-test-timer unit/unix_timer_tests.cc) - target_link_libraries(${PROJECT_NAME}-test-timer ${PROJECT_NAME}) + catkin_add_gtest(${PROJECT_NAME}-test-timer unit/unix_timer_tests.cc) + target_link_libraries(${PROJECT_NAME}-test-timer ${PROJECT_NAME}) endif() +elseif (UNIX) + + add_executable(serial_test + unix_serial_tests.cc + ) + target_link_libraries(serial_test + ${PROJECT_NAME} gtest gtest_main pthread + ) + set_target_properties(serial_test + PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED YES + ) + if (NOT APPLE) + target_link_libraries(serial_test util) + endif () + + add_executable(timer_test + unit/unix_timer_tests.cc + ) + target_link_libraries(timer_test + ${PROJECT_NAME} gtest gtest_main pthread + ) + set_target_properties(timer_test + PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED YES + ) + + add_test(NAME serial_test COMMAND serial_test) + add_test(NAME timer_test COMMAND timer_test) endif() diff --git a/tests/unit/unix_timer_tests.cc b/tests/unit/unix_timer_tests.cc index 5bbd1edc..6e38ab3e 100644 --- a/tests/unit/unix_timer_tests.cc +++ b/tests/unit/unix_timer_tests.cc @@ -1,13 +1,31 @@ +#include +#include +#include + #include "gtest/gtest.h" #include "serial/impl/unix.h" -#include -#include - using serial::MillisecondTimer; namespace { +typedef std::chrono::high_resolution_clock Clock; + + +template +std::chrono::milliseconds as_milliseconds(T duration) +{ + return std::chrono::duration_cast(duration); +} + + +struct test_timer { + MillisecondTimer timer; + std::chrono::time_point start; + test_timer(unsigned value): start(Clock::now()), timer(value) {} +}; + + /** * Do 100 trials of timing gaps between 0 and 19 milliseconds. * Expect accuracy within one millisecond. @@ -15,48 +33,45 @@ namespace { TEST(timer_tests, short_intervals) { for (int trial = 0; trial < 100; trial++) { - uint32_t ms = rand() % 20; - MillisecondTimer mt(ms); - usleep(1000 * ms); - int32_t r = mt.remaining(); - - // 1ms slush, for the cost of calling usleep. - EXPECT_NEAR(r+1, 0, 1); + int ms = rand() % 20; + auto t1 = Clock::now(); + MillisecondTimer mt(ms); + std::this_thread::sleep_for(std::chrono::milliseconds(ms)); + int elapsed = as_milliseconds(Clock::now() - t1).count(); + EXPECT_NEAR(ms - elapsed, mt.remaining(), 1); } } + TEST(timer_tests, overlapping_long_intervals) { - MillisecondTimer* timers[10]; - // Experimentally determined. Corresponds to the extra time taken by the loops, - // the big usleep, and the test infrastructure itself. - const int slush_factor = 14; + std::vector timers; - // Set up the timers to each time one second, 1ms apart. - for (int t = 0; t < 10; t++) - { - timers[t] = new MillisecondTimer(1000); - usleep(1000); + // Set up the timers to each time one second, 1 ms apart. + while (timers.size() < 10) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + struct test_timer timer(1000); // 1 s + timers.push_back(timer); } - // Check in on them after 500ms. - usleep(500000); - for (int t = 0; t < 10; t++) - { - EXPECT_NEAR(timers[t]->remaining(), 500 - slush_factor + t, 5); + // Check in on them after 500 ms. + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + for (auto& t: timers) { + auto elapsed = as_milliseconds(Clock::now() - t.start).count(); + EXPECT_NEAR(1000 - elapsed, t.timer.remaining(), 1); } - // Check in on them again after another 500ms and free them. - usleep(500000); - for (int t = 0; t < 10; t++) - { - EXPECT_NEAR(timers[t]->remaining(), -slush_factor + t, 5); - delete timers[t]; + // Check in on them again after another 500 ms. + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + for (auto& t: timers) { + auto elapsed = as_milliseconds(Clock::now() - t.start).count(); + EXPECT_NEAR(1000 - elapsed, t.timer.remaining(), 1); } } } // namespace + int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); diff --git a/tests/unix_serial_tests.cc b/tests/unix_serial_tests.cc index 26ffde2d..c34868a2 100644 --- a/tests/unix_serial_tests.cc +++ b/tests/unix_serial_tests.cc @@ -20,8 +20,6 @@ void loop() #include #include "gtest/gtest.h" -#include - // Use FRIEND_TEST... its not as nasty, thats what friends are for // // OMG this is so nasty... // #define private public