diff --git a/conf/CMakeLists.txt b/conf/CMakeLists.txt index 6c02f6887..9403b6dcf 100644 --- a/conf/CMakeLists.txt +++ b/conf/CMakeLists.txt @@ -1,12 +1,16 @@ # Used only for internal testing. -set(ign_library_path "${CMAKE_BINARY_DIR}/test/lib/ruby/ignition/cmd${IGN_DESIGNATION}${PROJECT_VERSION_MAJOR}") +set(ign_library_path "${CMAKE_BINARY_DIR}/test/lib/$/ruby/ignition/cmd${IGN_DESIGNATION}${PROJECT_VERSION_MAJOR}") # Generate a configuration file for internal testing. # Note that the major version of the library is included in the name. # Ex: transport0.yaml configure_file( "${IGN_DESIGNATION}.yaml.in" - "${CMAKE_BINARY_DIR}/test/conf/${IGN_DESIGNATION}${PROJECT_VERSION_MAJOR}.yaml" @ONLY) + "${CMAKE_CURRENT_BINARY_DIR}/${IGN_DESIGNATION}${PROJECT_VERSION_MAJOR}.yaml.configured" @ONLY) + +file(GENERATE + OUTPUT "${CMAKE_BINARY_DIR}/test/conf/$/${IGN_DESIGNATION}${PROJECT_VERSION_MAJOR}.yaml" + INPUT "${CMAKE_CURRENT_BINARY_DIR}/${IGN_DESIGNATION}${PROJECT_VERSION_MAJOR}.yaml.configured") # Used for the installed version. set(ign_library_path "${CMAKE_INSTALL_PREFIX}/lib/ruby/ignition/cmd${IGN_DESIGNATION}${PROJECT_VERSION_MAJOR}") diff --git a/configure.bat b/configure.bat deleted file mode 100644 index 87df2654e..000000000 --- a/configure.bat +++ /dev/null @@ -1,32 +0,0 @@ - -:: NOTE: This script is only meant to be used as part of the ignition developers' CI system -:: Users and developers should build and install this library using cmake and Visual Studio - - -:: Install dependencies -call %win_lib% :download_unzip_install libzmq-4.2.3_cppzmq-4.2.2_vc15-x64-dll-MD.zip -call %win_lib% :download_unzip_install sqlite-3.22.0-vc15-Win64-dll-MD.zip -call %win_lib% :install_ign_project ign-msgs main - -:: Set configuration variables -@set build_type=Release -@if not "%1"=="" set build_type=%1 -@echo Configuring for build type %build_type% - -:: Go to the directory that this configure.bat file exists in -cd /d %~dp0 - -:: Create a build directory and configure -md build -cd build -cmake .. ^ - -G "NMake Makefiles" ^ - -DCMAKE_INSTALL_PREFIX="%WORKSPACE_INSTALL_DIR%" ^ - -DCMAKE_PREFIX_PATH="%WORKSPACE_INSTALL_DIR%" ^ - -DCMAKE_BUILD_TYPE="%build_type%" ^ - --trace ^ - -DBUILD_TESTING:BOOL=False -:: Note: We disable testing by default. If the intention is for the CI to build and test -:: this project, then the CI script will turn it back on. - -:: If the caller wants to build and/or install, they should do so after calling this script diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 97772404f..4f95769b2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,12 +2,6 @@ # "gtest_sources" variable. ign_get_libsources_and_unittests(sources gtest_sources) -# Skip command line tests for Windows, see -# https://github.com/ignitionrobotics/ign-transport/issues/104 -if (MSVC) - list(REMOVE_ITEM gtest_sources ign_TEST.cc) -endif() - if (MSVC) # Warning #4251 is the "dll-interface" warning that tells you when types used # by a class are not being exported. These generated source files have private @@ -66,6 +60,4 @@ if(MSVC) endif() # Command line support. -if(NOT WIN32) - add_subdirectory(cmd) -endif() +add_subdirectory(cmd) diff --git a/src/cmd/CMakeLists.txt b/src/cmd/CMakeLists.txt index 643bb0d42..e809cbc30 100644 --- a/src/cmd/CMakeLists.txt +++ b/src/cmd/CMakeLists.txt @@ -1,13 +1,76 @@ +# Collect source files into the "sources" variable and unit test files into the +# "gtest_sources" variable. +ign_get_libsources_and_unittests(sources gtest_sources) + +# Skip command line tests for Windows, see +# https://github.com/ignitionrobotics/ign-transport/issues/104 +if (MSVC) + list(REMOVE_ITEM gtest_sources ign_TEST.cc) +endif() + +# Make a small static lib of command line functions +add_library(ign STATIC ign.cc) +target_link_libraries(ign + ${PROJECT_LIBRARY_TARGET_NAME} +) + +# Build topic CLI executable +set(topic_executable ign-transport-topic) +add_executable(${topic_executable} topic_main.cc) +target_link_libraries(${topic_executable} + ign + ignition-utils${IGN_UTILS_VER}::cli + ${PROJECT_LIBRARY_TARGET_NAME} +) +install(TARGETS ${topic_executable} DESTINATION ${IGN_LIB_INSTALL_DIR}/ignition/${IGN_DESIGNATION}${PROJECT_VERSION_MAJOR}/) + +# Build service CLI executable +set(service_executable ign-transport-service) +add_executable(${service_executable} service_main.cc) +target_link_libraries(${service_executable} + ign + ignition-utils${IGN_UTILS_VER}::cli + ${PROJECT_LIBRARY_TARGET_NAME} +) +install(TARGETS ${service_executable} DESTINATION ${IGN_LIB_INSTALL_DIR}/ignition/${IGN_DESIGNATION}${PROJECT_VERSION_MAJOR}/) + +# Build the unit tests. +ign_build_tests(TYPE UNIT SOURCES ${gtest_sources} + TEST_LIST test_list + LIB_DEPS ${EXTRA_TEST_LIB_DEPS}) + +foreach(test ${test_list}) + target_link_libraries(${test} ign) + + # Inform each test of its output directory so it knows where to call the + # auxiliary files from. Using a generator expression here is useful for + # multi-configuration generators, like Visual Studio. + target_compile_definitions(${test} PRIVATE + "DETAIL_IGN_TRANSPORT_TEST_DIR=\"$\"" + "IGN_TEST_LIBRARY_PATH=\"$\"") + +endforeach() + +if (TARGET UNIT_ign_TEST) + set_tests_properties( + UNIT_ign_TEST + PROPERTIES + ENVIRONMENT + "IGN_CONFIG_PATH=${CMAKE_BINARY_DIR}/test/conf/$" + ) +endif() + #=============================================================================== # Generate the ruby script for internal testing. # Note that the major version of the library is included in the name. # Ex: cmdtransport0.rb -set(cmd_script_generated_test "${CMAKE_BINARY_DIR}/test/lib/ruby/ignition/cmd${IGN_DESIGNATION}${PROJECT_VERSION_MAJOR}.rb") -set(cmd_script_configured_test "${cmd_script_generated_test}.configured") +set(cmd_script_generated_test "${CMAKE_BINARY_DIR}/test/lib/$/ruby/ignition/cmd${IGN_DESIGNATION}${PROJECT_VERSION_MAJOR}.rb") +set(cmd_script_configured_test "${CMAKE_CURRENT_BINARY_DIR}/test_cmd${IGN_DESIGNATION}${PROJECT_VERSION_MAJOR}.rb.configured") # Set the library_location variable to the full path of the library file within # the build directory. -set(library_location "$") +set(service_exe_location "$") +set(topic_exe_location "$") configure_file( "cmd${IGN_DESIGNATION}.rb.in" @@ -29,7 +92,8 @@ set(cmd_script_configured "${cmd_script_generated}.configured") # Set the library_location variable to the relative path to the library file # within the install directory structure. -set(library_location "../../../${CMAKE_INSTALL_LIBDIR}/$") +set(service_exe_location "../../../${CMAKE_INSTALL_LIBDIR}/ignition/${IGN_DESIGNATION}${PROJECT_VERSION_MAJOR}/$") +set(topic_exe_location "../../../${CMAKE_INSTALL_LIBDIR}/ignition/${IGN_DESIGNATION}${PROJECT_VERSION_MAJOR}/$") configure_file( "cmd${IGN_DESIGNATION}.rb.in" diff --git a/src/cmd/cmdtransport.rb.in b/src/cmd/cmdtransport.rb.in index a8b4d3a6c..e9f0b80e9 100644 --- a/src/cmd/cmdtransport.rb.in +++ b/src/cmd/cmdtransport.rb.in @@ -1,4 +1,4 @@ -#!/usr/bin/ruby +#!/usr/bin/env ruby # Copyright (C) 2014 Open Source Robotics Foundation # @@ -14,353 +14,53 @@ # See the License for the specific language governing permissions and # limitations under the License. -# We use 'dl' for Ruby <= 1.9.x and 'fiddle' for Ruby >= 2.0.x -if RUBY_VERSION.split('.')[0] < '2' - require 'dl' - require 'dl/import' - include DL -else - require 'fiddle' - require 'fiddle/import' - include Fiddle -end - -require 'optparse' +require 'open3' # Constants. -LIBRARY_NAME = '@library_location@' LIBRARY_VERSION = '@PROJECT_VERSION_FULL@' -COMMON_OPTIONS = - " -h [ --help ] Print this help message.\n"\ - " \n" + - " --force-version Use a specific library version.\n"\ - " \n" + - ' --versions Show the available versions.' -COMMANDS = { 'topic' => - "Print information about topics.\n\n" + - " ign topic [options]\n\n" + - "Options:\n\n" + - " -i [ --info ] Get info about a topic.\n"+ - " Requires the -t option.\n"+ - " \n"+ - " -l [ --list ] List all topics.\n" + - " \n"+ - " -t [ --topic ] arg Name of a topic.\n" + - " Required with -i, -p.\n" + - " \n"+ - " -m [ --msgtype ] arg Type of message to " + - "publish.\n" + - " Arg is a message type.\n" + - " Required with -p.\n" + - " \n"+ - " -p [ --pub ] arg Publish a message.\n" + - " arg is the message data." + - " The format expected is\n " + - " the same used by Protobuf DebugString(). E.g.:\n\n" + - " ign topic -t /foo -m " + - " ignition.msgs.StringMsg\n " + - " -p \'data:\"Custom data\"\'\n\n" + - " Requires -t and -m.\n" + - " \n"+ - " -e [ --echo ] Output data to screen." + - " E.g.:\n\n" + - " ign topic -e -t /foo\n" + - " \n"+ - " Requires -t.\n" + - " \n"+ - " -d [--duration] arg Duration (seconds) to run"+ - ". Applicable with \n" + - " echo. This will override "+ - "-n.\n" + - " \n"+ - " -n [--num] arg Number of messages to " + - "echo and then exit. A value \n" + - " " + - "<=0 implies infinite messages. Applicable with \n" + - " " + - "echo. This is overriden by -d.\n" + - "\n" + - COMMON_OPTIONS, - 'service' => - "Print information about services.\n\n" + - " ign service [options]\n\n" + - "Options:\n\n" + - " -i [ --info ] Get info about a service."+ - "\n" + - " Requires the -s option.\n"+ - " \n"+ - " -l [ --list ] List all services.\n" + - " \n"+ - " -s [ --service ] arg Name of a service.\n" + - " Arg is a service name.\n" + - " Required with -i, -r.\n" + - " \n"+ - " -r [ --req ] arg Request a service.\n" + - " Arg is the input data." + - " The format expected is\n " + - " the same used by Protobuf DebugString(). E.g.:\n\n" + - " ign service -s /echo\n" + - " --reqtype ignition.msgs"+ - ".StringMsg\n" + - " --reptype ignition.msgs"+ - ".StringMsg\n" + - " --timeout 2000 --req \'"+ - "data: \"Hello\"\n\n" + - " Requires -s, --timeout,\n"+ - " --reqtype, --reptype.\n" + - " \n"+ - " --timeout arg Timeout in milliseconds." + - "\n Arg is a timeout in " + - "milliseconds.\n" + - " Required with -r.\n" + - " \n"+ - " --reqtype arg Type of a request.\n" + - " Arg is the request type." + - " \n\n"+ - " --reptype arg Type of a response.\n" + - " Arg is the response type."+ - " \n\n"+ - COMMON_OPTIONS - } +COMMANDS = { + "service" => "@service_exe_location@", + "topic" => "@topic_exe_location@", +} # # Class for the Ignition transport command line tools. # class Cmd - # - # Return a structure describing the options. - # - def parse(args) - options = {} - - usage = COMMANDS[args[0]] - - # Read the command line arguments. - opt_parser = OptionParser.new do |opts| - opts.banner = usage - - opts.on('-h', '--help', 'Print this help message') do - puts usage - exit(0) - end - opts.on('-l', '--list', 'Print information about topics') do |l| - options['list'] = l - end - - opts.on('-t topic', '--topic', String, - 'Topic name') do |t| - options['topic'] = t - end - - opts.on('-s service', '--service', String, - 'Service name') do |t| - options['service'] = t - end - - opts.on('-m type', '--msgtype', String, - 'Message type') do |t| - options['msgtype'] = t - end - - opts.on('-p data', '--pub', String, - 'Publish a message') do |t| - options['pub'] = t - end - - opts.on('-i', '--info', String, - 'Print information about a topic') do |t| - options['info'] = t - end - - opts.on('-r data', '--req', String, - 'Request a data') do |t| - options['req'] = t - end - - opts.on('--timeout data', Integer, - 'Timeout') do |t| - options['timeout'] = t - end - - opts.on('--reqtype type', String, - 'Request type') do |t| - options['reqtype'] = t - end - - opts.on('--reptype type', String, - 'Response type') do |t| - options['reptype'] = t - end - - opts.on('-e', '--echo', String, - 'Output topic data to screen') do |e| - options['echo'] = e - end - - opts.on('-d secs', '--duration', Float, - 'Duration (seconds) to run') do |d| - options['duration'] = d - end - - opts.on('-n num', '--num', Integer, - 'Number of messages to echo') do |n| - options['num'] = n - end - - end - begin - opt_parser.parse!(args) - rescue - puts usage - exit(-1) - end - - # Check that there is at least one command and there is a plugin that knows - # how to handle it. - if ARGV.empty? || !COMMANDS.key?(ARGV[0]) || - options.empty? - puts usage - exit(-1) - end - - options['command'] = ARGV[0] - - options - end # parse() - def execute(args) - options = parse(args) + command = args[0] + exe_name = COMMANDS[command] - # puts 'Parsed:' - # puts options - - # Read the plugin that handles the command. - if LIBRARY_NAME[0] == '/' + if exe_name[0] == '/' # If the first character is a slash, we'll assume that we've been given an - # absolute path to the library. This is only used during test mode. - plugin = LIBRARY_NAME + # absolute path to the executable. This is only used during test mode. else # We're assuming that the library path is relative to the current # location of this script. - plugin = File.expand_path(File.join(File.dirname(__FILE__), LIBRARY_NAME)) + exe_name = File.expand_path(File.join(File.dirname(__FILE__), exe_name)) end conf_version = LIBRARY_VERSION - - begin - Importer.dlload plugin - rescue DLError - puts "Library error: [#{plugin}] not found." - exit(-1) - end - - # Read the library version. - Importer.extern 'char* ignitionVersion()' - begin - plugin_version = Importer.ignitionVersion.to_s - rescue DLError - puts "Library error: Problem running 'ignitionVersion()' from #{plugin}." - exit(-1) - end + exe_version = `#{exe_name} --version`.strip # Sanity check: Verify that the version of the yaml file matches the version # of the library that we are using. - unless plugin_version.eql? conf_version + unless exe_version.eql? conf_version puts "Error: Version mismatch. Your configuration file version is - [#{conf_version}] but #{plugin} version is [#{plugin_version}]." + [#{conf_version}] but #{exe_name} version is [#{exe_version}]." exit(-1) end - begin - case options['command'] - when 'topic' - if options.key?('list') - Importer.extern 'void cmdTopicList()' - Importer.cmdTopicList - elsif options.key?('info') - if not options.key?('topic') - puts 'ign topic --info: missing topic name (-t )' - puts 'Try ign topic --help' - else - Importer.extern 'void cmdTopicInfo(const char *)' - Importer.cmdTopicInfo(options['topic']) - end - elsif options.key?('pub') - if not options.key?('topic') - puts 'ign topic --pub: missing topic name (-t )' - puts 'Try ign topic --help' - elsif not options.key?('msgtype') - puts 'ign topic --pub: missing message type (--msgtype )' - puts 'Try ign topic --help' - else - Importer.extern 'void cmdTopicPub(const char *, const char *, const char *)' - Importer.cmdTopicPub(options['topic'], options['msgtype'], - options['pub']) - end - elsif options.key?('echo') - if not options.key?('topic') - puts 'ign topic --echo: missing topic name (-t )' - puts 'Try ign topic --help' - else - duration = -1.0 - count = -1 - if options.key?('duration') - duration = options['duration'] - end - if options.key?('num') - count = options['num'] - end - - Importer.extern 'void cmdTopicEcho(const char*, double, int)' - topic = options['topic'] - Importer.cmdTopicEcho(topic, duration.to_f, count.to_i) - end - else - puts 'Command error: I do not have an implementation '\ - 'for this command.' - end - when 'service' - if options.key?('list') - Importer.extern 'void cmdServiceList()' - Importer.cmdServiceList - elsif options.key?('info') - if not options.key?('service') - puts 'ign service --info: missing service name (-s )' - puts 'Try ign service --help' - else - Importer.extern 'void cmdServiceInfo(const char *)' - Importer.cmdServiceInfo(options['service']) - end - elsif options.key?('req') - if not options.key?('service') - puts 'ign service --req: missing service name (-s )' - puts 'Try ign service --help' - elsif not options.key?('reqtype') - puts 'ign service --req: missing input type (--reqtype )' - puts 'Try ign service --help' - elsif not options.key?('reptype') - puts 'ign service --req: missing response type (--reptype )' - puts 'Try ign service --help' - elsif not options.key?('timeout') - puts 'ign service --req: missing timeout in ms (--timeout '\ - ')' - puts 'Try ign service --help' - else - Importer.extern 'void cmdServiceReq(const char *, const char *,'\ - 'const char *, int, const char *)' - Importer.cmdServiceReq(options['service'], options['reqtype'], - options['reptype'], options['timeout'], options['req']) - end - else - puts 'Command error: I do not have an implementation '\ - 'for this command.' + # Drop command from list of arguments + Open3.popen2e(exe_name, *args[1..-1]) do |_in, out_err, wait_thr| + begin + out_err.each do |line| + print line end - else - puts 'Command error: I do not have an implementation for '\ - "command [ign #{options['command']}]." + exit(wait_thr.value.exitstatus) + rescue Interrupt => e + print e.message + exit(-1) end - rescue - puts "Library error: Problem running [#{options['command']}]() "\ - "from #{plugin}." end end end diff --git a/src/ign.cc b/src/cmd/ign.cc similarity index 92% rename from src/ign.cc rename to src/cmd/ign.cc index 2fed3319a..61acc900e 100644 --- a/src/ign.cc +++ b/src/cmd/ign.cc @@ -42,7 +42,7 @@ using namespace ignition; using namespace transport; ////////////////////////////////////////////////// -extern "C" void IGNITION_TRANSPORT_VISIBLE cmdTopicList() +extern "C" void cmdTopicList() { Node node; @@ -54,7 +54,7 @@ extern "C" void IGNITION_TRANSPORT_VISIBLE cmdTopicList() } ////////////////////////////////////////////////// -extern "C" void IGNITION_TRANSPORT_VISIBLE cmdTopicInfo(const char *_topic) +extern "C" void cmdTopicInfo(const char *_topic) { if (!_topic || std::string(_topic).empty()) { @@ -89,7 +89,7 @@ extern "C" void IGNITION_TRANSPORT_VISIBLE cmdTopicInfo(const char *_topic) } ////////////////////////////////////////////////// -extern "C" void IGNITION_TRANSPORT_VISIBLE cmdServiceList() +extern "C" void cmdServiceList() { Node node; @@ -101,7 +101,7 @@ extern "C" void IGNITION_TRANSPORT_VISIBLE cmdServiceList() } ////////////////////////////////////////////////// -extern "C" void IGNITION_TRANSPORT_VISIBLE cmdServiceInfo(const char *_service) +extern "C" void cmdServiceInfo(const char *_service) { if (!_service || std::string(_service).empty()) { @@ -136,7 +136,7 @@ extern "C" void IGNITION_TRANSPORT_VISIBLE cmdServiceInfo(const char *_service) } ////////////////////////////////////////////////// -extern "C" void IGNITION_TRANSPORT_VISIBLE cmdTopicPub(const char *_topic, +extern "C" void cmdTopicPub(const char *_topic, const char *_msgType, const char *_msgData) { if (!_topic) @@ -187,7 +187,7 @@ extern "C" void IGNITION_TRANSPORT_VISIBLE cmdTopicPub(const char *_topic, } ////////////////////////////////////////////////// -extern "C" void IGNITION_TRANSPORT_VISIBLE cmdServiceReq(const char *_service, +extern "C" void cmdServiceReq(const char *_service, const char *_reqType, const char *_repType, const int _timeout, const char *_reqData) { @@ -250,7 +250,7 @@ extern "C" void IGNITION_TRANSPORT_VISIBLE cmdServiceReq(const char *_service, } ////////////////////////////////////////////////// -extern "C" void IGNITION_TRANSPORT_VISIBLE cmdTopicEcho(const char *_topic, +extern "C" void cmdTopicEcho(const char *_topic, const double _duration, int _count) { if (!_topic || std::string(_topic).empty()) @@ -299,7 +299,7 @@ extern "C" void IGNITION_TRANSPORT_VISIBLE cmdTopicEcho(const char *_topic, } ////////////////////////////////////////////////// -extern "C" const char IGNITION_TRANSPORT_VISIBLE *ignitionVersion() +extern "C" const char *ignitionVersion() { return IGNITION_TRANSPORT_VERSION_FULL; } diff --git a/src/ign.hh b/src/cmd/ign.hh similarity index 85% rename from src/ign.hh rename to src/cmd/ign.hh index 242a44e32..00817c43d 100644 --- a/src/ign.hh +++ b/src/cmd/ign.hh @@ -24,17 +24,17 @@ /// \brief External hook to execute 'ign topic -i' from the command line. /// \param[in] _topic Topic name. -extern "C" void IGNITION_TRANSPORT_VISIBLE cmdTopicInfo(const char *_topic); +extern "C" void cmdTopicInfo(const char *_topic); /// \brief External hook to execute 'ign service -i' from the command line. /// \param[in] _service Service name. -extern "C" void IGNITION_TRANSPORT_VISIBLE cmdServiceInfo(const char *_service); +extern "C" void cmdServiceInfo(const char *_service); /// \brief External hook to execute 'ign topic -l' from the command line. -extern "C" void IGNITION_TRANSPORT_VISIBLE cmdTopicList(); +extern "C" void cmdTopicList(); /// \brief External hook to execute 'ign service -l' from the command line. -extern "C" void IGNITION_TRANSPORT_VISIBLE cmdServiceList(); +extern "C" void cmdServiceList(); /// \brief External hook to execute 'ign topic -p' from the command line. /// \param[in] _topic Topic name. @@ -43,7 +43,7 @@ extern "C" void IGNITION_TRANSPORT_VISIBLE cmdServiceList(); /// DebugString(). /// E.g.: cmdTopicPub("/foo", "ignition.msgs.StringMsg", /// "'data:\"Custom data\"'); -extern "C" void IGNITION_TRANSPORT_VISIBLE cmdTopicPub(const char *_topic, +extern "C" void cmdTopicPub(const char *_topic, const char *_msgType, const char *_msgData); @@ -57,7 +57,7 @@ extern "C" void IGNITION_TRANSPORT_VISIBLE cmdTopicPub(const char *_topic, /// E.g.: cmdServiceReq("/bar", "ignition.msgs.StringMsg", /// "ignition.msgs.StringMsg", 1000, /// "'data:\"Custom data\"'); -extern "C" void IGNITION_TRANSPORT_VISIBLE cmdServiceReq(const char *_service, +extern "C" void cmdServiceReq(const char *_service, const char *_reqType, const char *_repType, const int _timeout, @@ -71,12 +71,12 @@ extern "C" void IGNITION_TRANSPORT_VISIBLE cmdServiceReq(const char *_service, /// \param[in] _count Number of messages to echo and then stop. A value <= 0 /// indicates no limit. The _duration parameter overrides the _count /// parameter. -extern "C" void IGNITION_TRANSPORT_VISIBLE cmdTopicEcho(const char *_topic, +extern "C" void cmdTopicEcho(const char *_topic, const double _duration, int _count); /// \brief External hook to read the library version. /// \return C-string representing the version. Ex.: 0.1.2 -extern "C" const char IGNITION_TRANSPORT_VISIBLE *ignitionVersion(); +extern "C" const char *ignitionVersion(); #endif diff --git a/src/ign_TEST.cc b/src/cmd/ign_TEST.cc similarity index 94% rename from src/ign_TEST.cc rename to src/cmd/ign_TEST.cc index f4ba5bf27..9d346345a 100644 --- a/src/ign_TEST.cc +++ b/src/cmd/ign_TEST.cc @@ -327,13 +327,21 @@ TEST(ignTest, TopicPublish) // Check the 'ign topic -p' command. std::string ign = std::string(IGN_PATH) + "/ign"; - std::string output = custom_exec_str(ign + - " topic -t /bar -m ign_msgs.StringMsg -p 'data:\"good_value\"' " + - g_ignVersion); - - ASSERT_TRUE(output.empty()) << output; - std::this_thread::sleep_for(std::chrono::milliseconds(300)); + std::string output; + unsigned int retries = 0; + while (g_topicCBStr != "good_value" && retries++ < 200u) + { + // Send on alternating retries + if (retries % 2) + { + output = custom_exec_str(ign + + " topic -t /bar -m ign_msgs.StringMsg -p 'data:\"good_value\"' " + + g_ignVersion); + EXPECT_TRUE(output.empty()) << output; + } + std::this_thread::sleep_for(std::chrono::milliseconds(30)); + } EXPECT_EQ(g_topicCBStr, "good_value"); // Try to publish a message not included in Ignition Messages. @@ -345,10 +353,17 @@ TEST(ignTest, TopicPublish) // Try to publish using an incorrect topic name. error = "Topic [/] is not valid"; + output = custom_exec_str(ign + + " topic -t / -m ign_msgs.StringMsg -p 'data:\"good_value\"' "+ + g_ignVersion); + EXPECT_EQ(output.compare(0, error.size(), error), 0) << output; + + // Try to publish using an incorrect number of arguments. + error = "The following argument was not expected: wrong_topic"; output = custom_exec_str(ign + " topic -t / wrong_topic -m ign_msgs.StringMsg -p 'data:\"good_value\"' "+ g_ignVersion); - EXPECT_EQ(output.compare(0, error.size(), error), 0); + EXPECT_EQ(output.compare(0, error.size(), error), 0) << output; } ////////////////////////////////////////////////// @@ -453,10 +468,6 @@ int main(int argc, char **argv) // Set the partition name for this process. setenv("IGN_PARTITION", g_partition.c_str(), 1); - // Set IGN_CONFIG_PATH to the directory where the .yaml configuration files - // is located. - setenv("IGN_CONFIG_PATH", IGN_CONFIG_PATH, 1); - // Make sure that we load the library recently built and not the one installed // in your system. // Save the current value of LD_LIBRARY_PATH. diff --git a/src/ign_src_TEST.cc b/src/cmd/ign_src_TEST.cc similarity index 100% rename from src/ign_src_TEST.cc rename to src/cmd/ign_src_TEST.cc diff --git a/src/cmd/service_main.cc b/src/cmd/service_main.cc new file mode 100644 index 000000000..8ebda3ab1 --- /dev/null +++ b/src/cmd/service_main.cc @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include + +#include "ign.hh" + +#include + +////////////////////////////////////////////////// +/// \brief Enumeration of available commands +enum class ServiceCommand +{ + kNone, + kServiceList, + kServiceInfo, + kServiceReq, +}; + +////////////////////////////////////////////////// +/// \brief Structure to hold all available service options +struct ServiceOptions +{ + /// \brief Command to execute + ServiceCommand command{ServiceCommand::kNone}; + + /// \brief Name of the service + std::string service{""}; + + /// \brief Data used with a service request + std::string reqData{""}; + + /// \brief Request type to use when requesting + std::string reqType{""}; + + /// \brief Response type to use when requesting + std::string repType{""}; + + /// \brief Timeout to use when requesting (in milliseconds) + int timeout{-1}; +}; + +////////////////////////////////////////////////// +/// \brief Callback fired when options are successfully parsed +void runServiceCommand(const ServiceOptions &_opt) +{ + switch(_opt.command) + { + case ServiceCommand::kServiceList: + cmdServiceList(); + break; + case ServiceCommand::kServiceInfo: + cmdServiceInfo(_opt.service.c_str()); + break; + case ServiceCommand::kServiceReq: + cmdServiceReq(_opt.service.c_str(), + _opt.reqType.c_str(), _opt.repType.c_str(), + _opt.timeout, _opt.reqData.c_str()); + break; + case ServiceCommand::kNone: + default: + // In the event that there is no command, display help + throw CLI::CallForHelp(); + break; + } +} + +////////////////////////////////////////////////// +void addServiceFlags(CLI::App &_app) +{ + auto opt = std::make_shared(); + + auto serviceOpt = _app.add_option("-s,--service", + opt->service, "Name of a service"); + auto reqTypeOpt = _app.add_option("--reqtype", + opt->reqType, "Type of a request."); + auto repTypeOpt = _app.add_option("--reptype", + opt->repType, "Type of a response."); + auto timeoutOpt = _app.add_option("--timeout", + opt->timeout, "Timeout in milliseconds."); + + auto command = _app.add_option_group("command", "Command to be executed"); + command->add_flag_callback("-l,--list", + [opt](){ + opt->command = ServiceCommand::kServiceList; + }, "List available services"); + + command->add_flag_callback("-i,--info", + [opt](){ + opt->command = ServiceCommand::kServiceInfo; + }, "Get information about a service") + ->needs(serviceOpt); + + command->add_option_function("-r,--req", + [opt](const std::string &_reqData){ + opt->command = ServiceCommand::kServiceReq; + opt->reqData = _reqData; + }, "Perform a service request") + ->needs(serviceOpt) + ->needs(reqTypeOpt) + ->needs(repTypeOpt) + ->needs(timeoutOpt); + + _app.callback([opt](){runServiceCommand(*opt); }); +} + +////////////////////////////////////////////////// +int main(int argc, char** argv) +{ + CLI::App app{"Introspect Ignition services"}; + + app.set_help_all_flag("--help-all", "Show all help"); + + app.add_flag_callback("-v,--version", [](){ + std::cout << IGNITION_TRANSPORT_VERSION_FULL << std::endl; + throw CLI::Success(); + }); + + addServiceFlags(app); + CLI11_PARSE(app, argc, argv); +} diff --git a/src/cmd/topic_main.cc b/src/cmd/topic_main.cc new file mode 100644 index 000000000..6e1c7ab8b --- /dev/null +++ b/src/cmd/topic_main.cc @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include + +#include "ign.hh" + +#include + +////////////////////////////////////////////////// +/// \brief Enumeration of available commands +enum class TopicCommand +{ + kNone, + kTopicList, + kTopicInfo, + kTopicPub, + kTopicEcho +}; + +////////////////////////////////////////////////// +/// \brief Structure to hold all available topic options +struct TopicOptions +{ + /// \brief Command to execute + TopicCommand command{TopicCommand::kNone}; + + /// \brief Name of the topic + std::string topic{""}; + + /// \brief Message type to use when publishing + std::string msgType{""}; + + /// \brief Message data to use when publishing + std::string msgData{""}; + + /// \brief Amount of time to echo (in seconds) + double duration{-1}; + + /// \brief Number of messages to echo + int count{-1}; +}; + +////////////////////////////////////////////////// +/// \brief Callback fired when options are successfully parsed +void runTopicCommand(const TopicOptions &_opt) +{ + switch(_opt.command) + { + case TopicCommand::kTopicList: + cmdTopicList(); + break; + case TopicCommand::kTopicInfo: + cmdTopicInfo(_opt.topic.c_str()); + break; + case TopicCommand::kTopicPub: + cmdTopicPub(_opt.topic.c_str(), + _opt.msgType.c_str(), + _opt.msgData.c_str()); + break; + case TopicCommand::kTopicEcho: + cmdTopicEcho(_opt.topic.c_str(), _opt.duration, _opt.count); + break; + case TopicCommand::kNone: + default: + // In the event that there is no command, display help + throw CLI::CallForHelp(); + } +} + +////////////////////////////////////////////////// +void addTopicFlags(CLI::App &_app) +{ + auto opt = std::make_shared(); + + auto topicOpt = _app.add_option("-t,--topic", + opt->topic, "Name of a topic"); + auto msgTypeOpt = _app.add_option("-m,--msgtype", + opt->msgType, "Type of message to publish"); + auto durationOpt = _app.add_option("-d,--duration", + opt->duration, + "Duration (seconds) to run"); + auto countOpt = _app.add_option("-n,--num", + opt->count, + "Numer of messages to echo and then exit"); + + durationOpt->excludes(countOpt); + countOpt->excludes(durationOpt); + + auto command = _app.add_option_group("command", "Command to be executed"); + + command->add_flag_callback("-l,--list", + [opt](){ + opt->command = TopicCommand::kTopicList; + }); + + command->add_flag_callback("-i,--info", + [opt](){ + opt->command = TopicCommand::kTopicInfo; + }) + ->needs(topicOpt); + + command->add_flag_callback("-e,--echo", + [opt](){ + opt->command = TopicCommand::kTopicEcho; + }); + + command->add_option_function("-p,--pub", + [opt](const std::string &_msgData){ + opt->command = TopicCommand::kTopicPub; + opt->msgData = _msgData; + }) + ->needs(topicOpt) + ->needs(msgTypeOpt); + + _app.callback([opt](){runTopicCommand(*opt); }); +} + +////////////////////////////////////////////////// +int main(int argc, char** argv) +{ + CLI::App app{"Introspect Ignition topics"}; + + app.add_flag_callback("-v,--version", [](){ + std::cout << IGNITION_TRANSPORT_VERSION_FULL << std::endl; + throw CLI::Success(); + }); + + addTopicFlags(app); + CLI11_PARSE(app, argc, argv); +}