From bbe2128abe84ec7238ae13f2bda7c5d84927ec59 Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Sun, 23 Jan 2022 17:30:08 -0300 Subject: [PATCH] build: more ns3 and CMake fixes Includes: - inform ns3 user cmake was not found, or an unsupported version was found - replace open-mpi environment variables with command-line arguments - mark more variables as advanced - add -v and --verbose option to run and only output the programs output by default - fix scratch subdir prefixes and add tests - prevent cmake crash when scratch sources do not have a main function - disable MPI module by default - wrap cmakelists_content in quotes to prevent failure of filter_libraries - install pkgconfig files and add tests - pkg-config generation (still missing installation) - forward PYTHONPATH to modulegen - fix dependency search for brite, click and openflow --- CMakeLists.txt | 2 +- .../custom_modules/ns3_cmake_package.cmake | 80 +++++- .../custom_modules/ns3_coverage.cmake | 3 +- .../custom_modules/ns3_module_macros.cmake | 3 +- .../custom_modules/ns3_versioning.cmake | 1 + buildsupport/macros_and_definitions.cmake | 49 +++- buildsupport/pkgconfig_template.pc.in | 13 + ns3 | 66 +++-- scratch/CMakeLists.txt | 30 ++- src/brite/CMakeLists.txt | 20 +- src/click/CMakeLists.txt | 29 +-- src/openflow/CMakeLists.txt | 30 +-- utils/tests/test-ns3.py | 236 ++++++++++++++---- 13 files changed, 416 insertions(+), 146 deletions(-) create mode 100644 buildsupport/pkgconfig_template.pc.in diff --git a/CMakeLists.txt b/CMakeLists.txt index a3aefa3cd..f50aaa62d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,7 +73,7 @@ option(NS3_LINK_TIME_OPTIMIZATION "Build with link-time optimization" OFF) option(NS3_MONOLIB "Build a single shared ns-3 library and link it against executables" OFF ) -option(NS3_MPI "Build with MPI support" ON) +option(NS3_MPI "Build with MPI support" OFF) option(NS3_NATIVE_OPTIMIZATIONS "Build with -march=native -mtune=native" OFF) set(NS3_OUTPUT_DIRECTORY "" CACHE STRING "Directory to store built artifacts") option(NS3_PRECOMPILE_HEADERS diff --git a/buildsupport/custom_modules/ns3_cmake_package.cmake b/buildsupport/custom_modules/ns3_cmake_package.cmake index b7bdf0fda..f1142d29c 100644 --- a/buildsupport/custom_modules/ns3_cmake_package.cmake +++ b/buildsupport/custom_modules/ns3_cmake_package.cmake @@ -15,6 +15,75 @@ # # Author: Gabriel Ferreira +function(build_required_and_libs_lists module_name visibility libraries + all_ns3_libraries +) + set(linked_libs_list) + set(required_modules_list) + foreach(lib ${libraries}) + if(${lib} IN_LIST all_ns3_libraries) + get_target_property(lib_real_name ${lib} OUTPUT_NAME) + string(REPLACE "lib" "" required_module_name ${lib}) + set(required_modules_list + "${required_modules_list} ns3-${required_module_name}" + ) + else() + set(lib_real_name ${lib}) + endif() + set(linked_libs_list "${linked_libs_list} -l${lib_real_name}") + endforeach() + set(pkgconfig_${visibility}_libs ${linked_libs_list} PARENT_SCOPE) + set(pkgconfig_${visibility}_required ${required_modules_list} PARENT_SCOPE) +endfunction() + +function(pkgconfig_module libname) + # Fetch all libraries that will be linked to module + get_target_property(all_libs ${libname} LINK_LIBRARIES) + + # Then fetch public libraries + get_target_property(interface_libs ${libname} INTERFACE_LINK_LIBRARIES) + + # Filter linking flags + string(REPLACE "${LIB_AS_NEEDED_PRE}" "" all_libs "${all_libs}") + string(REPLACE "${LIB_AS_NEEDED_POST}" "" all_libs "${all_libs}") + string(REPLACE "${LIB_AS_NEEDED_PRE}" "" interface_libs "${interface_libs}") + string(REPLACE "${LIB_AS_NEEDED_POST}" "" interface_libs "${interface_libs}") + + foreach(interface_lib ${interface_libs}) + list(REMOVE_ITEM all_libs ${interface_lib}) + endforeach() + set(private_libs ${all_libs}) + + # Create two lists of publicly and privately linked libraries to this module + string(REPLACE "lib" "" module_name ${libname}) + + # These filter out ns and non-ns libraries into public and private libraries + # linked against module_name + get_target_property(pkgconfig_target_lib ${libname} OUTPUT_NAME) + + # pkgconfig_public_libs pkgconfig_public_required + build_required_and_libs_lists( + "${module_name}" public "${interface_libs}" + "${ns3-libs};${ns3-contrib-libs}" + ) + + # pkgconfig_private_libs pkgconfig_private_required + build_required_and_libs_lists( + "${module_name}" private "${private_libs}" + "${ns3-libs};${ns3-contrib-libs}" + ) + + # Configure pkgconfig file for the module using pkgconfig variables + set(pkgconfig_file ${CMAKE_BINARY_DIR}/pkgconfig/ns3-${module_name}.pc) + configure_file( + ${PROJECT_SOURCE_DIR}/buildsupport/pkgconfig_template.pc.in + ${pkgconfig_file} @ONLY + ) + + # Set file to be installed + install(FILES ${pkgconfig_file} DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) +endfunction() + function(ns3_cmake_package) # Only create configuration to export if there is an module configured to be # built @@ -27,6 +96,14 @@ function(ns3_cmake_package) return() endif() + # CMake does not support '-' separated versions in config packages, so replace + # them with dots + string(REPLACE "-" "." ns3_version "${NS3_VER}") + + foreach(library ${ns3-libs}${ns3-contrib-libs}) + pkgconfig_module(${library}) + endforeach() + install( EXPORT ns3ExportTargets NAMESPACE ns3:: @@ -41,9 +118,6 @@ function(ns3_cmake_package) PATH_VARS CMAKE_INSTALL_LIBDIR ) - # CMake does not support '-' separated versions in config packages, so replace - # them with dots - string(REPLACE "-" "." ns3_version "${NS3_VER}") write_basic_package_version_file( ${CMAKE_CURRENT_BINARY_DIR}/ns3ConfigVersion.cmake VERSION ${ns3_version} COMPATIBILITY ExactVersion diff --git a/buildsupport/custom_modules/ns3_coverage.cmake b/buildsupport/custom_modules/ns3_coverage.cmake index 9af092d07..9db00498b 100644 --- a/buildsupport/custom_modules/ns3_coverage.cmake +++ b/buildsupport/custom_modules/ns3_coverage.cmake @@ -16,13 +16,14 @@ # Author: Gabriel Ferreira if(${NS3_COVERAGE}) - + mark_as_advanced(GCOVp) find_program(GCOVp gcov) if(GCOVp) add_definitions(--coverage) link_libraries(-lgcov) endif() + mark_as_advanced(LCOVp) find_program(LCOVp lcov) if(NOT LCOVp) message(FATAL_ERROR "LCOV is required but it is not installed.") diff --git a/buildsupport/custom_modules/ns3_module_macros.cmake b/buildsupport/custom_modules/ns3_module_macros.cmake index cf72c439d..b2b01eee0 100644 --- a/buildsupport/custom_modules/ns3_module_macros.cmake +++ b/buildsupport/custom_modules/ns3_module_macros.cmake @@ -324,7 +324,8 @@ macro( ) execute_process( COMMAND - ${CMAKE_COMMAND} -E env PYTHONPATH=${CMAKE_OUTPUT_DIRECTORY} + ${CMAKE_COMMAND} -E env + PYTHONPATH=${CMAKE_OUTPUT_DIRECTORY}:$ENV{PYTHONPATH} ${modulegen_modular_command} ${CMAKE_CURRENT_SOURCE_DIR} ${arch} ${prefix}${libname_sub} ${module_src} TIMEOUT 60 diff --git a/buildsupport/custom_modules/ns3_versioning.cmake b/buildsupport/custom_modules/ns3_versioning.cmake index fd2cfbdf5..67d2bc4c6 100644 --- a/buildsupport/custom_modules/ns3_versioning.cmake +++ b/buildsupport/custom_modules/ns3_versioning.cmake @@ -66,6 +66,7 @@ function(check_ns3_closest_tags CLOSEST_TAG VERSION_TAG_DISTANCE endfunction() function(configure_embedded_version) + mark_as_advanced(GIT) find_program(GIT git) if(${NS3_ENABLE_BUILD_VERSION} AND (NOT GIT)) message(FATAL_ERROR "Embedding build version into libraries require Git.") diff --git a/buildsupport/macros_and_definitions.cmake b/buildsupport/macros_and_definitions.cmake index 628c4bf8a..72aee86d0 100644 --- a/buildsupport/macros_and_definitions.cmake +++ b/buildsupport/macros_and_definitions.cmake @@ -256,6 +256,7 @@ function(check_deps package_deps program_deps missing_deps) # CMake likes to cache find_* to speed things up, so we can't reuse names # here or it won't check other dependencies string(TOUPPER ${program} upper_${program}) + mark_as_advanced(${upper_${program}}) find_program(${upper_${program}} ${program}) if(NOT ${upper_${program}}) list(APPEND local_missing_deps ${program}) @@ -1196,12 +1197,13 @@ function(recursive_dependency module_name) file(READ ${contrib_cmakelist} cmakelists_content) set(contrib TRUE) else() + set(cmakelists_content "") message( STATUS "The CMakeLists.txt file for module ${module_name} was not found." ) endif() - filter_libraries(${cmakelists_content} matches) + filter_libraries("${cmakelists_content}" matches) # Add this visited module dependencies to the dependencies list if(contrib) @@ -1389,6 +1391,51 @@ function(parse_ns3rc enabled_modules examples_enabled tests_enabled) endif() endfunction(parse_ns3rc) +function(find_external_library_header_and_library name header_name library_name + search_paths +) + mark_as_advanced(${name}_library) + find_library( + ${name}_library ${library_name} HINTS ${search_paths} ENV LD_LIBRARY_PATH + PATH_SUFFIXES /build /lib /build/lib / + ) + set(${name}_library_dir) + if(${name}_library) + get_filename_component(${name}_library_dir ${${name}_library} DIRECTORY + )# e.g. lib/openflow.(so|dll|dylib|a) -> lib + endif() + mark_as_advanced(${name}_header) + find_file( + ${name}_header ${header_name} + HINTS ${search_paths} ${${name}_library_dir}/../ + ${${name}_library_dir}/../../ + PATH_SUFFIXES + /build + /include + /build/include + /build/include/${name} + /include/${name} + /${name} + / + ) + # If we find both library and header, we export their values + if(${name}_library AND ${name}_header) + get_filename_component(header_include_dir ${${name}_header} DIRECTORY + )# e.g. include/click/ (simclick.h) -> #include should work + get_filename_component(header_include_dir2 ${header_include_dir} DIRECTORY + )# e.g. include/(click) -> #include should work + set(${name}_include_directories + "${header_include_dir};${header_include_dir2}" PARENT_SCOPE + ) + set(${name}_library ${${name}_library} PARENT_SCOPE) + set(${name}_header ${${name}_header} PARENT_SCOPE) + else() + set(${name}_include_directories PARENT_SCOPE) + set(${name}_library PARENT_SCOPE) + set(${name}_header PARENT_SCOPE) + endif() +endfunction() + # Waf workaround scripts include(buildsupport/custom_modules/waf_workaround_c4cache.cmake) include(buildsupport/custom_modules/waf_workaround_buildstatus.cmake) diff --git a/buildsupport/pkgconfig_template.pc.in b/buildsupport/pkgconfig_template.pc.in new file mode 100644 index 000000000..dda4eea79 --- /dev/null +++ b/buildsupport/pkgconfig_template.pc.in @@ -0,0 +1,13 @@ +exec_prefix="@CMAKE_INSTALL_FULL_BINDIR@" +libdir="@CMAKE_INSTALL_FULL_LIBDIR@" +includedir="@CMAKE_INSTALL_FULL_INCLUDEDIR@" + +Name: ns3-@module_name@ +Description: @CMAKE_PROJECT_DESCRIPTION@ +URL: @CMAKE_PROJECT_HOMEPAGE_URL@ +Version: @ns3_version@ +Requires: @pkgconfig_public_required@ +Requires.private: @pkgconfig_private_required@ +Cflags: -I"${includedir}" +Libs: -L"${libdir}" -l@pkgconfig_target_lib@ @pkgconfig_public_libs@ +Libs.private: -L"${libdir}" @pkgconfig_private_libs@ \ No newline at end of file diff --git a/ns3 b/ns3 index e90991eeb..90dbe432d 100755 --- a/ns3 +++ b/ns3 @@ -14,20 +14,20 @@ out_dir = os.sep.join([ns3_path, "build"]) lock_file = os.sep.join([ns3_path, ".lock-waf_%s_build" % sys.platform]) print_buffer = "" -run_only = False +verbose = True # Prints everything in the print_buffer on exit def exit_handler(dry_run): - global print_buffer, run_only - # We should not print anything in run_only a.k.a. run-no-build cases, except if dry_run - if not dry_run and run_only: + global print_buffer, verbose + # We should not print anything in run except if dry_run or verbose + if not dry_run and not verbose: return if print_buffer == "": return if dry_run: print("The following commands would be executed:") - elif not run_only: + elif verbose: print("Finished executing the following commands:") print(print_buffer[1:]) @@ -152,7 +152,7 @@ def parse_args(argv): action="store_true", default=None, dest="configure_dry_run") parser_clean = sub_parser.add_parser('clean', help='Removes files created by waf and ns3') - parser_clean.add_argument('clean', action="store_true", default=False) + parser_clean.add_argument('clean', action="store_true", default=False) parser_clean.add_argument('--dry-run', help="Do not execute the commands", action="store_true", default=None, dest="clean_dry_run") @@ -195,6 +195,10 @@ def parse_args(argv): help='Use sudo to setup suid bits on ns3 executables.', dest='enable_sudo', action='store_true', default=False) + parser_run.add_argument('-v', '--verbose', + help='Print which commands were executed', + dest='run_verbose', action='store_true', + default=False) parser_shell = sub_parser.add_parser('shell', help='Try "./ns3 shell --help" for more runtime options') @@ -263,8 +267,8 @@ def parse_args(argv): args_separator_index = argv.index('--') args.program_args = argv[args_separator_index + 1:] except ValueError: - msg = "Unknown options were given: {options}.\n"\ - "To see the allowed options add the `--help` option.\n"\ + msg = "Unknown options were given: {options}.\n" \ + "To see the allowed options add the `--help` option.\n" \ "To forward configuration or runtime options, put them after '--'.\n" if args.run: msg += "Try: ./ns3 run {target} -- {options}\n" @@ -552,15 +556,31 @@ def get_program_shortcuts(build_profile, ns3_version): return ns3_program_map -def cmake_build(current_cmake_cache_folder, output, jobs, target=None, dry_run=False): +def parse_version(version_str): + version = version_str.split(".") + version = tuple(map(int, version)) + return version + + +def cmake_check_version(): # Check CMake version cmake = shutil.which("cmake") if not cmake: - raise Exception("CMake was not found") - version = re.findall("version (.*)\n", subprocess.check_output([cmake, "--version"]).decode("utf-8"))[0] + print("Error: CMake not found; please install version 3.10 or greater, or modify $PATH") + exit(-1) + cmake_output = subprocess.check_output([cmake, "--version"]).decode("utf-8") + version = re.findall("version (.*)", cmake_output)[0] + if parse_version(version) < parse_version("3.10.0"): + print("Error: CMake found at %s but version %s is older than 3.10" % (cmake, version)) + exit(-1) + return cmake, version + + +def cmake_build(current_cmake_cache_folder, output, jobs, target=None, dry_run=False): + _, version = cmake_check_version() # Older CMake versions don't accept the number of jobs directly - jobs_part = ("-j %d" % jobs) if version >= "3.12.0" else "" + jobs_part = ("-j %d" % jobs) if parse_version(version) >= parse_version("3.12.0") else "" target_part = (" --target %s" % target) if target else "" cmake_build_command = "cmake --build . %s%s" % (jobs_part, target_part) @@ -653,9 +673,7 @@ def get_target_to_build(program_path, ns3_version, build_profile): def configuration_step(current_cmake_cache_folder, current_cmake_generator, args, output, dry_run=False): # Search for the CMake binary - cmake = shutil.which("cmake") - if not cmake: - raise Exception("CMake was not found") + cmake, _ = cmake_check_version() # If --force-refresh, we load settings from the CMakeCache, delete it, then reconfigure CMake to # force refresh cached packages/libraries that were installed/removed, without losing the current settings @@ -790,9 +808,15 @@ def run_step(args, target_to_run, target_args): target_to_run = commands[0] target_args = commands[1:] + target_args + # running mpi on the CI? + if target_to_run in ["mpiexec", "mpirun"] and os.getenv("MPI_CI"): + if shutil.which("ompi_info"): + target_args = ["--oversubscribe"] + target_args + target_args = ["--allow-run-as-root"] + target_args + program_arguments = [*debugging_software, target_to_run, *target_args] - if not run_only or args.dry_run: + if verbose or args.dry_run: exported_variables = "export " for (variable, value) in custom_env.items(): if variable == "PATH": @@ -890,7 +914,7 @@ def refuse_run_as_root(): def main(): - global out_dir, run_only + global out_dir, verbose # Refuse to run with sudo refuse_run_as_root() @@ -940,6 +964,14 @@ def main(): run_only = False build_and_run = False if args.run: + # When running, default to not verbose + verbose = args.run_verbose + + # If not verbose, silence the rest of the script + if not verbose: + output = subprocess.DEVNULL + + # Check whether we are only running or we need to build first if not args.no_build: build_and_run = True else: diff --git a/scratch/CMakeLists.txt b/scratch/CMakeLists.txt index 4b4476a61..4a495d35e 100644 --- a/scratch/CMakeLists.txt +++ b/scratch/CMakeLists.txt @@ -7,29 +7,35 @@ function(create_scratch source_files) return() endif() - # If the scratch has more than a source file, we need - # to find the source with the main function - unset(scratch_src) + # If the scratch has more than a source file, we need to find the source with + # the main function + set(scratch_src) foreach(source_file ${source_files}) - file(READ ${source_file} source_file_contents) - string(REGEX MATCHALL "main[(| (]" main_position "${source_file_contents}") - if(CMAKE_MATCH_0) - set(scratch_src ${source_file}) - endif() + file(READ ${source_file} source_file_contents) + string(REGEX MATCHALL "main[(| (]" main_position "${source_file_contents}") + if(CMAKE_MATCH_0) + set(scratch_src ${source_file}) + endif() endforeach() + if(NOT scratch_src) + return() + endif() + # Get parent directory name get_filename_component(scratch_dirname ${scratch_src} DIRECTORY) - string(REPLACE "${CMAKE_CURRENT_SOURCE_DIR}/" "" scratch_dirname - ${scratch_dirname} + string(REPLACE "${CMAKE_CURRENT_SOURCE_DIR}" "" scratch_dirname + "${scratch_dirname}" ) + string(REPLACE "/" "_" scratch_dirname "${scratch_dirname}") + # Get source name get_filename_component(scratch_name ${scratch_src} NAME_WE) set(target_prefix scratch_) - if(${scratch_dirname}) + if(scratch_dirname) # Join the names together if dirname is not the scratch folder - set(target_prefix scratch_${scratch_dirname}_) + set(target_prefix scratch${scratch_dirname}_) endif() # Get source absolute path and transform into relative path diff --git a/src/brite/CMakeLists.txt b/src/brite/CMakeLists.txt index 89b98950a..25f433451 100644 --- a/src/brite/CMakeLists.txt +++ b/src/brite/CMakeLists.txt @@ -1,24 +1,16 @@ set(NS3_WITH_BRITE "" CACHE PATH "Build with brite support") set(NS3_BRITE "OFF" CACHE INTERNAL "ON if Brite is found in NS3_WITH_BRITE") -if(NOT NS3_WITH_BRITE) - return() -endif() -find_library( - brite_dep brite PATHS ${NS3_WITH_BRITE} PATH_SUFFIXES /build /build/lib /lib +find_external_library_header_and_library( + "brite" "Brite.h" "brite" "${NS3_WITH_BRITE}" ) -find_file(brite_header Brite.h HINTS ${NS3_WITH_BRITE} - PATH_SUFFIXES /build /build/include /include -) - -if(NOT (brite_dep AND brite_header)) - message(STATUS "Brite was not found in ${NS3_WITH_BRITE}") +if(NOT (brite_library AND brite_header)) + message(STATUS "Brite was not found") return() endif() # Only process module if include folder and library have been found -get_filename_component(brite_include_folder ${brite_header} DIRECTORY) -include_directories(${brite_include_folder}) +include_directories(${brite_include_directories}) set(NS3_BRITE "ON" CACHE INTERNAL "ON if Brite is found in NS3_WITH_BRITE") set(name brite) @@ -29,7 +21,7 @@ set(header_files helper/brite-topology-helper.h) # link to dependencies set(libraries_to_link ${libnetwork} ${libcore} ${libinternet} - ${libpoint-to-point} ${brite_dep} + ${libpoint-to-point} ${brite_library} ) set(test_sources test/brite-test-topology.cc) diff --git a/src/click/CMakeLists.txt b/src/click/CMakeLists.txt index a14f1eb1b..0adc166d7 100644 --- a/src/click/CMakeLists.txt +++ b/src/click/CMakeLists.txt @@ -1,30 +1,15 @@ set(NS3_WITH_CLICK "" CACHE PATH "Build with click support") set(NS3_CLICK "OFF" CACHE INTERNAL "ON if Click is found in NS3_WITH_CLICK") -if(NOT NS3_WITH_CLICK) +find_external_library_header_and_library( + "click" "simclick.h" "click" "${NS3_WITH_CLICK}" +) +if(NOT (click_library AND click_header)) + message(STATUS "Click was not found") return() endif() -find_library( - click_dep click PATHS ${NS3_WITH_CLICK} PATH_SUFFIXES /build /build/lib /lib -) -find_file(click_header simclick.h HINTS ${NS3_WITH_CLICK} - PATH_SUFFIXES /build /include /build/include /build/include/click - /include/click -) - -if(NOT (click_dep AND click_header)) - message(STATUS "Click was not found in ${NS3_WITH_CLICK}") - return() -endif() - -get_filename_component( - openflow_header_include_folder ${openflow_header} DIRECTORY -) # include/click/ (simclick.h) -get_filename_component( - openflow_header_include_folder ${openflow_header_include_folder} DIRECTORY -) # include/(click) -include_directories(${openflow_header_include_folder}) +include_directories(${click_include_directories}) set(NS3_CLICK "ON" CACHE INTERNAL "ON if Click is found in NS3_WITH_CLICK") add_definitions(-DNS3_CLICK) @@ -38,7 +23,7 @@ set(header_files helper/click-internet-stack-helper.h model/ipv4-click-routing.h model/ipv4-l3-click-protocol.h ) -set(libraries_to_link ${libcore} ${libnetwork} ${libinternet} ${click_dep}) +set(libraries_to_link ${libcore} ${libnetwork} ${libinternet} ${click_library}) set(test_sources test/ipv4-click-routing-test.cc) diff --git a/src/openflow/CMakeLists.txt b/src/openflow/CMakeLists.txt index 306a08381..de3057cd6 100644 --- a/src/openflow/CMakeLists.txt +++ b/src/openflow/CMakeLists.txt @@ -3,31 +3,21 @@ set(NS3_OPENFLOW "OFF" CACHE INTERNAL "ON if Openflow is found in NS3_WITH_OPENFLOW" ) -if(NOT NS3_WITH_OPENFLOW) +find_external_library_header_and_library( + "openflow" "openflow.h" "openflow" "${NS3_WITH_OPENFLOW}" +) +if(NOT (openflow_library AND openflow_header)) + message(STATUS "Openflow was not found") return() endif() -find_library( - openflow_dep openflow PATHS ${NS3_WITH_OPENFLOW} PATH_SUFFIXES /build /lib - /build/lib -) -find_file(openflow_header openflow.h HINTS ${NS3_WITH_OPENFLOW} - PATH_SUFFIXES /build /include /build/include /build/include/openflow - /include/openflow -) - -if(NOT (openflow_dep AND openflow_header)) - message(STATUS "Openflow was not found in ${NS3_WITH_OPENFLOW}") +check_include_file_cxx(boost/static_assert.hpp BOOST_STATIC_ASSERT) +if(NOT BOOST_STATIC_ASSERT) + message(STATUS "Openflow requires Boost static_assert.hpp") return() endif() -get_filename_component( - openflow_header_include_folder ${openflow_header} DIRECTORY -) # include/openflow/ (openflow.h) -get_filename_component( - openflow_header_include_folder ${openflow_header_include_folder} DIRECTORY -) # include/(openflow) -include_directories(${openflow_header_include_folder}) +include_directories(${openflow_include_directories}) add_definitions(-DNS3_OPENFLOW -DENABLE_OPENFLOW) set(NS3_OPENFLOW "ON" CACHE INTERNAL "ON if Openflow is found in NS3_WITH_OPENFLOW" @@ -43,7 +33,7 @@ set(header_files helper/openflow-switch-helper.h model/openflow-interface.h model/openflow-switch-net-device.h ) -set(libraries_to_link ${libinternet} ${openflow_dep}) +set(libraries_to_link ${libinternet} ${openflow_library}) set(test_sources test/openflow-switch-test-suite.cc) diff --git a/utils/tests/test-ns3.py b/utils/tests/test-ns3.py index c81ec0e01..942a56450 100644 --- a/utils/tests/test-ns3.py +++ b/utils/tests/test-ns3.py @@ -225,7 +225,7 @@ class NS3RunWafTargets(unittest.TestCase): Try to run a different executable built by waf @return None """ - return_code, stdout, stderr = run_ns3("run command-line-example --no-build") + return_code, stdout, stderr = run_ns3("run command-line-example --verbose --no-build") self.assertEqual(return_code, 0) self.assertIn("command-line-example", stdout) @@ -552,14 +552,14 @@ class NS3ConfigureTestCase(NS3BaseTestCase): @return None """ # Try filtering disabled modules to disable lte and modules that depend on it. - return_code, stdout, stderr = run_ns3("configure --disable-modules='lte;mpi'") + return_code, stdout, stderr = run_ns3("configure --disable-modules='lte;wimax'") self.config_ok(return_code, stdout) # At this point we should have fewer modules. enabled_modules = get_enabled_modules() self.assertLess(len(enabled_modules), len(self.ns3_modules)) self.assertNotIn("ns3-lte", enabled_modules) - self.assertNotIn("ns3-mpi", enabled_modules) + self.assertNotIn("ns3-wimax", enabled_modules) # Try cleaning the list of enabled modules to reset to the normal configuration. return_code, stdout, stderr = run_ns3("configure --disable-modules=''") @@ -696,9 +696,9 @@ class NS3ConfigureTestCase(NS3BaseTestCase): # Run all cases and then check outputs return_code0, stdout0, stderr0 = run_ns3("--dry-run run scratch-simulator") - return_code1, stdout1, stderr1 = run_ns3("run scratch-simulator") + return_code1, stdout1, stderr1 = run_ns3("run scratch-simulator --verbose") return_code2, stdout2, stderr2 = run_ns3("--dry-run run scratch-simulator --no-build") - return_code3, stdout3, stderr3 = run_ns3("run scratch-simulator --no-build ") + return_code3, stdout3, stderr3 = run_ns3("run scratch-simulator --no-build") # Return code and stderr should be the same for all of them. self.assertEqual(sum([return_code0, return_code1, return_code2, return_code3]), 0) @@ -779,6 +779,118 @@ class NS3ConfigureTestCase(NS3BaseTestCase): self.assertEqual(return_code, 0) self.assertIn("ns-3 version:", stdout) + def test_13_Scratches(self): + """! + Test if CMake target names for scratches and ns3 shortcuts + are working correctly + """ + + test_files = ["scratch/main.cc", + "scratch/empty.cc", + "scratch/subdir1/main.cc", + "scratch/subdir2/main.cc"] + + # Create test scratch files + for path in test_files: + filepath = os.path.join(ns3_path, path) + os.makedirs(os.path.dirname(filepath), exist_ok=True) + with open(filepath, "w") as f: + if "main" in path: + f.write("int main (int argc, char *argv[]){}") + else: + # no main function will prevent this target from + # being created, we should skip it and continue + # processing without crashing + f.write("") + + # Reload the cmake cache to pick them up + return_code, stdout, stderr = run_ns3("configure") + self.assertEqual(return_code, 0) + + # Try to build them with ns3 and cmake + for path in test_files: + path = path.replace(".cc", "") + return_code1, stdout1, stderr1 = run_program("cmake", "--build . --target %s" + % path.replace("/", "_"), + cwd=os.path.join(ns3_path, "cmake_cache")) + return_code2, stdout2, stderr2 = run_ns3("build %s" % path) + if "main" in path: + self.assertEqual(return_code1, 0) + self.assertEqual(return_code2, 0) + else: + self.assertEqual(return_code1, 2) + self.assertEqual(return_code2, 1) + + # Try to run them + for path in test_files: + path = path.replace(".cc", "") + return_code, stdout, stderr = run_ns3("run %s --no-build" % path) + if "main" in path: + self.assertEqual(return_code, 0) + else: + self.assertEqual(return_code, 1) + + # Delete the test files and reconfigure to clean them up + for path in test_files: + source_absolute_path = os.path.join(ns3_path, path) + os.remove(source_absolute_path) + if "empty" in path: + continue + filename = os.path.basename(path).replace(".cc", "") + executable_absolute_path = os.path.dirname(os.path.join(ns3_path, "build", path)) + executable_name = list(filter(lambda x: filename in x, + os.listdir(executable_absolute_path) + ) + )[0] + + os.remove(os.path.join(executable_absolute_path, executable_name)) + if path not in ["scratch/main.cc", "scratch/empty.cc"]: + os.rmdir(os.path.dirname(source_absolute_path)) + + return_code, stdout, stderr = run_ns3("configure") + self.assertEqual(return_code, 0) + + def test_14_MpiCommandTemplate(self): + """! + Test if ns3 is inserting additional arguments by MPICH and OpenMPI to run on the CI + """ + # Skip test if mpi is not installed + if shutil.which("mpiexec") is None: + return + + # Ensure sample simulator was built + return_code, stdout, stderr = run_ns3("build sample-simulator") + self.assertEqual(return_code, 0) + + # Get executable path + sample_simulator_path = list(filter(lambda x: "sample-simulator" in x, self.ns3_executables))[0] + + mpi_command = "--dry-run run sample-simulator --command-template=\"mpiexec -np 2 %s\"" + non_mpi_command = "--dry-run run sample-simulator --command-template=\"echo %s\"" + + # Get the commands to run sample-simulator in two processes with mpi + return_code, stdout, stderr = run_ns3(mpi_command) + self.assertEqual(return_code, 0) + self.assertIn("mpiexec -np 2 %s" % sample_simulator_path, stdout) + + # Get the commands to run sample-simulator in two processes with mpi, now with the environment variable + return_code, stdout, stderr = run_ns3(mpi_command, env={"MPI_CI": "1"}) + self.assertEqual(return_code, 0) + if shutil.which("ompi_info"): + self.assertIn("mpiexec --allow-run-as-root --oversubscribe -np 2 %s" % sample_simulator_path, stdout) + else: + self.assertIn("mpiexec --allow-run-as-root -np 2 %s" % sample_simulator_path, stdout) + + # Now we repeat for the non-mpi command + return_code, stdout, stderr = run_ns3(non_mpi_command) + self.assertEqual(return_code, 0) + self.assertIn("echo %s" % sample_simulator_path, stdout) + + # Again the non-mpi command, with the MPI_CI environment variable set + return_code, stdout, stderr = run_ns3(non_mpi_command, env={"MPI_CI": "1"}) + self.assertEqual(return_code, 0) + self.assertIn("echo %s" % sample_simulator_path, stdout) + class NS3BuildBaseTestCase(NS3BaseTestCase): """! @@ -1039,47 +1151,63 @@ class NS3BuildBaseTestCase(NS3BaseTestCase): # specifying ns3-01 (text version with 'dev' is not supported) # and specifying ns3-00 (a wrong version) for version in ["", "3.01", "3.00"]: - test_cmake_project = """ - cmake_minimum_required(VERSION 3.10..3.10) - project(ns3_consumer CXX) - - list(APPEND CMAKE_PREFIX_PATH ./{lib}/cmake/ns3) - find_package(ns3 {version} COMPONENTS libcore) - add_executable(test main.cpp) - target_link_libraries(test PRIVATE ns3::libcore) - """.format(lib=("lib64" if lib64 else "lib"), version=version) + find_package_import = """ + list(APPEND CMAKE_PREFIX_PATH ./{lib}/cmake/ns3) + find_package(ns3 {version} COMPONENTS libcore) + target_link_libraries(test PRIVATE ns3::libcore) + """.format(lib=("lib64" if lib64 else "lib"), version=version) + pkgconfig_import = """ + list(APPEND CMAKE_PREFIX_PATH ./) + include(FindPkgConfig) + pkg_check_modules(ns3 REQUIRED IMPORTED_TARGET ns3-core{version}) + target_link_libraries(test PUBLIC PkgConfig::ns3) + """.format(lib=("lib64" if lib64 else "lib"), + version="="+version if version else "" + ) - test_cmake_project_file = os.sep.join([install_prefix, "CMakeLists.txt"]) - with open(test_cmake_project_file, "w") as f: - f.write(test_cmake_project) + for import_type in [pkgconfig_import, find_package_import]: + test_cmake_project = """ + cmake_minimum_required(VERSION 3.10..3.10) + project(ns3_consumer CXX) + add_executable(test main.cpp) + """ + import_type - # Configure the test project - cmake = shutil.which("cmake") - return_code, stdout, stderr = run_program(cmake, - "-DCMAKE_BUILD_TYPE=debug .", - cwd=install_prefix) + test_cmake_project_file = os.sep.join([install_prefix, "CMakeLists.txt"]) + with open(test_cmake_project_file, "w") as f: + f.write(test_cmake_project) - if version == "3.00": - self.assertEqual(return_code, 1) - self.assertIn('Could not find a configuration file for package "ns3" that is compatible', - stderr.replace("\n", "")) - else: - self.assertEqual(return_code, 0) - self.assertIn("Build files", stdout) + # Configure the test project + cmake = shutil.which("cmake") + return_code, stdout, stderr = run_program(cmake, + "-DCMAKE_BUILD_TYPE=debug .", + cwd=install_prefix) + if version == "3.00": + self.assertEqual(return_code, 1) + if import_type == find_package_import: + self.assertIn('Could not find a configuration file for package "ns3" that is compatible', + stderr.replace("\n", "")) + elif import_type == pkgconfig_import: + self.assertIn('A required package was not found', + stderr.replace("\n", "")) + else: + raise Exception("Unknown import type") + else: + self.assertEqual(return_code, 0) + self.assertIn("Build files", stdout) - # Build the test project making use of import ns-3 - return_code, stdout, stderr = run_program("cmake", "--build .", cwd=install_prefix) + # Build the test project making use of import ns-3 + return_code, stdout, stderr = run_program("cmake", "--build .", cwd=install_prefix) - if version == "3.00": - self.assertEqual(return_code, 2) - self.assertGreater(len(stderr), 0) - else: - self.assertEqual(return_code, 0) - self.assertIn("Built target", stdout) + if version == "3.00": + self.assertEqual(return_code, 2) + self.assertGreater(len(stderr), 0) + else: + self.assertEqual(return_code, 0) + self.assertIn("Built target", stdout) - # Try running the test program that imports ns-3 - return_code, stdout, stderr = run_program("./test", "", cwd=install_prefix) - self.assertEqual(return_code, 0) + # Try running the test program that imports ns-3 + return_code, stdout, stderr = run_program("./test", "", cwd=install_prefix) + self.assertEqual(return_code, 0) # Uninstall return_code, stdout, stderr = run_ns3("uninstall") @@ -1112,7 +1240,7 @@ class NS3BuildBaseTestCase(NS3BaseTestCase): self.assertIn(build_line, stdout) # Test if run is working - return_code, stdout, stderr = run_ns3("run %s" % target_to_run) + return_code, stdout, stderr = run_ns3("run %s --verbose" % target_to_run) self.assertEqual(return_code, 0) self.assertIn(build_line, stdout) stdout = stdout.replace("scratch_%s" % target_cmake, "") # remove build lines @@ -1174,7 +1302,7 @@ class NS3ExpectedUseTestCase(NS3BaseTestCase): Try to build and run test-runner @return None """ - return_code, stdout, stderr = run_ns3('run "test-runner --list"') + return_code, stdout, stderr = run_ns3('run "test-runner --list" --verbose') self.assertEqual(return_code, 0) self.assertIn("Built target test-runner", stdout) self.assertIn(cmake_build_target_command(target="test-runner"), stdout) @@ -1202,7 +1330,7 @@ class NS3ExpectedUseTestCase(NS3BaseTestCase): Try to run test-runner without building @return None """ - return_code, stdout, stderr = run_ns3('run "test-runner --list" --no-build ') + return_code, stdout, stderr = run_ns3('run "test-runner --list" --no-build --verbose') self.assertEqual(return_code, 0) self.assertNotIn("Built target test-runner", stdout) self.assertNotIn(cmake_build_target_command(target="test-runner"), stdout) @@ -1230,7 +1358,7 @@ class NS3ExpectedUseTestCase(NS3BaseTestCase): Test if scratch simulator is executed through gdb @return None """ - return_code, stdout, stderr = run_ns3("run scratch-simulator --gdb --no-build") + return_code, stdout, stderr = run_ns3("run scratch-simulator --gdb --verbose --no-build") self.assertEqual(return_code, 0) self.assertIn("scratch-simulator", stdout) self.assertIn("No debugging symbols found", stdout) @@ -1240,7 +1368,7 @@ class NS3ExpectedUseTestCase(NS3BaseTestCase): Test if scratch simulator is executed through valgrind @return None """ - return_code, stdout, stderr = run_ns3("run scratch-simulator --valgrind --no-build") + return_code, stdout, stderr = run_ns3("run scratch-simulator --valgrind --verbose --no-build") self.assertEqual(return_code, 0) self.assertIn("scratch-simulator", stderr) self.assertIn("Memcheck", stderr) @@ -1445,8 +1573,8 @@ class NS3ExpectedUseTestCase(NS3BaseTestCase): self.assertEqual(stderr2, stderr3) # Command templates with %s should at least continue and try to run the target - return_code4, stdout4, stderr4 = run_ns3('run sample-simulator --command-template "%s --PrintVersion"') - return_code5, stdout5, stderr5 = run_ns3('run sample-simulator --command-template="%s --PrintVersion"') + return_code4, stdout4, _ = run_ns3('run sample-simulator --command-template "%s --PrintVersion" --verbose') + return_code5, stdout5, _ = run_ns3('run sample-simulator --command-template="%s --PrintVersion" --verbose') self.assertEqual((return_code4, return_code5), (0, 0)) self.assertIn("sample-simulator --PrintVersion", stdout4) self.assertIn("sample-simulator --PrintVersion", stdout5) @@ -1459,9 +1587,9 @@ class NS3ExpectedUseTestCase(NS3BaseTestCase): """ # Test if all argument passing flavors are working - return_code0, stdout0, stderr0 = run_ns3('run "sample-simulator --help"') - return_code1, stdout1, stderr1 = run_ns3('run sample-simulator --command-template="%s --help"') - return_code2, stdout2, stderr2 = run_ns3('run sample-simulator -- --help') + return_code0, stdout0, stderr0 = run_ns3('run "sample-simulator --help" --verbose') + return_code1, stdout1, stderr1 = run_ns3('run sample-simulator --command-template="%s --help" --verbose') + return_code2, stdout2, stderr2 = run_ns3('run sample-simulator --verbose -- --help') self.assertEqual((return_code0, return_code1, return_code2), (0, 0, 0)) self.assertIn("sample-simulator --help", stdout0) @@ -1479,9 +1607,9 @@ class NS3ExpectedUseTestCase(NS3BaseTestCase): self.assertEqual(stderr1, stderr2) # Now collect results for each argument individually - return_code0, stdout0, stderr0 = run_ns3('run "sample-simulator --PrintGlobals"') - return_code1, stdout1, stderr1 = run_ns3('run "sample-simulator --PrintGroups"') - return_code2, stdout2, stderr2 = run_ns3('run "sample-simulator --PrintTypeIds"') + return_code0, stdout0, stderr0 = run_ns3('run "sample-simulator --PrintGlobals" --verbose') + return_code1, stdout1, stderr1 = run_ns3('run "sample-simulator --PrintGroups" --verbose') + return_code2, stdout2, stderr2 = run_ns3('run "sample-simulator --PrintTypeIds" --verbose') self.assertEqual((return_code0, return_code1, return_code2), (0, 0, 0)) self.assertIn("sample-simulator --PrintGlobals", stdout0) @@ -1489,7 +1617,7 @@ class NS3ExpectedUseTestCase(NS3BaseTestCase): self.assertIn("sample-simulator --PrintTypeIds", stdout2) # Then check if all the arguments are correctly merged by checking the outputs - cmd = 'run "sample-simulator --PrintGlobals" --command-template="%s --PrintGroups" -- --PrintTypeIds' + cmd = 'run "sample-simulator --PrintGlobals" --command-template="%s --PrintGroups" --verbose -- --PrintTypeIds' return_code, stdout, stderr = run_ns3(cmd) self.assertEqual(return_code, 0)