diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d3f46aac..3477c2f38 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,11 @@ project(NS3 CXX C) file(STRINGS VERSION NS3_VER) +# minimum compiler versions +set(AppleClang_MinVersion 7.0.0) +set(Clang_MinVersion 3.6.0) +set(GNU_MinVersion 7.0.0) + # common options option(NS3_ASSERT "Enable assert on failure" OFF) option(NS3_DES_METRICS "Enable DES Metrics event collection" OFF) diff --git a/buildsupport/3rd_party/FindHarfBuzz.cmake b/buildsupport/3rd_party/FindHarfBuzz.cmake index 534aff5a4..32d4a171d 100644 --- a/buildsupport/3rd_party/FindHarfBuzz.cmake +++ b/buildsupport/3rd_party/FindHarfBuzz.cmake @@ -27,8 +27,8 @@ # Try to find Harfbuzz include and library directories. # # After successful discovery, this will set for inclusion where needed: -# HarfBuzz_INCLUDE_DIRS - containg the HarfBuzz headers HarfBuzz_LIBRARIES - -# containg the HarfBuzz library +# HarfBuzz_INCLUDE_DIRS - contains the HarfBuzz headers HarfBuzz_LIBRARIES - +# contains the HarfBuzz library #[=======================================================================[.rst: FindHarfBuzz diff --git a/buildsupport/custom_modules/ns3_cmake_package.cmake b/buildsupport/custom_modules/ns3_cmake_package.cmake index 46c5835a0..b7bdf0fda 100644 --- a/buildsupport/custom_modules/ns3_cmake_package.cmake +++ b/buildsupport/custom_modules/ns3_cmake_package.cmake @@ -15,7 +15,18 @@ # # Author: Gabriel Ferreira -macro(ns3_cmake_package) +function(ns3_cmake_package) + # Only create configuration to export if there is an module configured to be + # built + set(enabled_modules "${ns3-libs};${ns3-contrib-libs}") + if(enabled_modules STREQUAL ";") + message( + STATUS + "No modules were configured, so we cannot create installation artifacts" + ) + return() + endif() + install( EXPORT ns3ExportTargets NAMESPACE ns3:: @@ -30,8 +41,11 @@ macro(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_VER} + ${CMAKE_CURRENT_BINARY_DIR}/ns3ConfigVersion.cmake VERSION ${ns3_version} COMPATIBILITY ExactVersion ) @@ -39,29 +53,13 @@ macro(ns3_cmake_package) "${CMAKE_CURRENT_BINARY_DIR}/ns3ConfigVersion.cmake" DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ns3 ) - - install(DIRECTORY ${CMAKE_HEADER_OUTPUT_DIRECTORY} - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} - ) - - # Hack to get the install_manifest.txt file before finishing the installation, - # which we can then install along with ns3 to make uninstallation trivial - set(sep "/") - install( - CODE "string(REPLACE \";\" \"\\n\" MY_CMAKE_INSTALL_MANIFEST_CONTENT \"\$\{CMAKE_INSTALL_MANIFEST_FILES\}\")\n\ - string(REPLACE \"/\" \"${sep}\" MY_CMAKE_INSTALL_MANIFEST_CONTENT_FINAL \"\$\{MY_CMAKE_INSTALL_MANIFEST_CONTENT\}\")\n\ - file(WRITE manifest.txt \"\$\{MY_CMAKE_INSTALL_MANIFEST_CONTENT_FINAL\}\")" - ) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/manifest.txt - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ns3 - ) -endmacro() +endfunction() # You will need administrative privileges to run this add_custom_target( uninstall COMMAND - rm -R `cat ${CMAKE_INSTALL_FULL_LIBDIR}/cmake/ns3/manifest.txt` && rm -R + rm `ls ${CMAKE_INSTALL_FULL_LIBDIR}/libns3*` && rm -R ${CMAKE_INSTALL_FULL_LIBDIR}/cmake/ns3 && rm -R ${CMAKE_INSTALL_FULL_INCLUDEDIR}/ns3 ) diff --git a/buildsupport/custom_modules/ns3_module_macros.cmake b/buildsupport/custom_modules/ns3_module_macros.cmake index e3f700279..7bdcf0305 100644 --- a/buildsupport/custom_modules/ns3_module_macros.cmake +++ b/buildsupport/custom_modules/ns3_module_macros.cmake @@ -81,6 +81,19 @@ macro( add_library(ns3::${lib${libname}} ALIAS ${lib${libname}}) + # Associate public headers with library for installation purposes + if("${libname}" STREQUAL "core") + set(config_headers ${CMAKE_HEADER_OUTPUT_DIRECTORY}/config-store-config.h + ${CMAKE_HEADER_OUTPUT_DIRECTORY}/core-config.h + ) + endif() + set_target_properties( + ${lib${libname}} + PROPERTIES + PUBLIC_HEADER + "${header_files};${deprecated_header_files};${config_headers};${CMAKE_HEADER_OUTPUT_DIRECTORY}/${libname}-module.h" + ) + if(${NS3_CLANG_TIMETRACE}) add_dependencies(timeTraceReport ${lib${libname}}) endif() @@ -148,7 +161,7 @@ macro( endif() # Build tests if requested - if(${TESTS_ENABLED}) + if(${ENABLE_TESTS}) list(LENGTH test_sources test_source_len) if(${test_source_len} GREATER 0) # Create libname of output library test of module @@ -188,7 +201,7 @@ macro( endif() # Build lib examples if requested - if(${EXAMPLES_ENABLED}) + if(${ENABLE_EXAMPLES}) if((EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/examples) AND (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/examples/CMakeLists.txt) ) @@ -376,8 +389,9 @@ macro( install( TARGETS ${lib${name}} EXPORT ns3ExportTargets - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/ + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/ + PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/ns3" ) endmacro() diff --git a/buildsupport/custom_modules/waf_workaround_c4cache.cmake b/buildsupport/custom_modules/waf_workaround_c4cache.cmake index dd6d170c5..ef877d1fc 100644 --- a/buildsupport/custom_modules/waf_workaround_c4cache.cmake +++ b/buildsupport/custom_modules/waf_workaround_c4cache.cmake @@ -59,8 +59,8 @@ function(generate_c4che_cachepy) cache_cmake_flag(NS3_NSC "NSC_ENABLED" cache_contents) # missing support cache_cmake_flag(ENABLE_REALTIME "ENABLE_REAL_TIME" cache_contents) cache_cmake_flag(NS3_PTHREAD "ENABLE_THREADING" cache_contents) - cache_cmake_flag(EXAMPLES_ENABLED "ENABLE_EXAMPLES" cache_contents) - cache_cmake_flag(TESTS_ENABLED "ENABLE_TESTS" cache_contents) + cache_cmake_flag(ENABLE_EXAMPLES "ENABLE_EXAMPLES" cache_contents) + cache_cmake_flag(ENABLE_TESTS "ENABLE_TESTS" cache_contents) cache_cmake_flag(NS3_OPENFLOW "ENABLE_OPENFLOW" cache_contents) cache_cmake_flag(NS3_CLICK "NSCLICK" cache_contents) cache_cmake_flag(NS3_BRITE "ENABLE_BRITE" cache_contents) diff --git a/buildsupport/custom_modules/waf_workaround_fakeconfig.cmake b/buildsupport/custom_modules/waf_workaround_fakeconfig.cmake index 341c61a10..d58bab6fc 100644 --- a/buildsupport/custom_modules/waf_workaround_fakeconfig.cmake +++ b/buildsupport/custom_modules/waf_workaround_fakeconfig.cmake @@ -63,10 +63,10 @@ macro(check_on_or_off user_config_switch confirmation_flag) if(${confirmation_flag}) string(APPEND out "ON\n") else() - string(APPEND out "OFF (not found)\n") + string(APPEND out "OFF (missing dependency)\n") endif() else() - string(APPEND out "OFF\n") + string(APPEND out "OFF (not requested)\n") endif() endmacro() @@ -111,14 +111,18 @@ macro(write_fakewaf_config) string(APPEND out "BRITE Integration : ") check_on_or_off("ON" "${NS3_BRITE}") - string(APPEND out "DES Metrics event collection : ${NS3_DES_METRICS}\n") + string(APPEND out "DES Metrics event collection : ") + check_on_or_off("${NS3_DES_METRICS}" "${NS3_DES_METRICS}") + string(APPEND out "DPDK NetDevice : ") check_on_or_off("ON" "${ENABLE_DPDKDEVNET}") string(APPEND out "Emulation FdNetDevice : ") - check_on_or_off("${NS3_EMU}" "${ENABLE_EMU}") + check_on_or_off("${ENABLE_EMU}" "${ENABLE_EMUNETDEV}") + + string(APPEND out "Examples : ") + check_on_or_off("${ENABLE_EXAMPLES}" "${ENABLE_EXAMPLES}") - string(APPEND out "Examples : ${EXAMPLES_ENABLED}\n") string(APPEND out "File descriptor NetDevice : ") check_on_or_off("ON" "${ENABLE_FDNETDEV}") @@ -141,7 +145,7 @@ macro(write_fakewaf_config) check_on_or_off("ON" "${NS3_OPENFLOW}") string(APPEND out "Netmap emulation FdNetDevice : ") - check_on_or_off("${NS3_EMU}" "${ENABLE_NETMAP_EMU}") + check_on_or_off("${ENABLE_EMU}" "${ENABLE_NETMAP_EMU}") string( APPEND @@ -153,21 +157,29 @@ macro(write_fakewaf_config) out "PlanetLab FdNetDevice : flag is set to ${NS3_PLANETLAB}, but currently not supported\n" ) - string(APPEND out "PyViz visualizer : ${NS3_VISUALIZER}\n") + string(APPEND out "PyViz visualizer : ") + check_on_or_off("${NS3_VISUALIZER}" "${ENABLE_VISUALIZER}") + # string(APPEND out "Python API Scanning Support : not enabled (castxml too # old) - string(APPEND out "Python Bindings : ${NS3_PYTHON_BINDINGS}\n") + string(APPEND out "Python Bindings : ") + check_on_or_off("${NS3_PYTHON_BINDINGS}" "${ENABLE_PYTHON_BINDINGS}") + string(APPEND out "Real Time Simulator : ") check_on_or_off("${NS3_REALTIME}" "${ENABLE_REALTIME}") string(APPEND out "SQLite stats support : ") check_on_or_off("${NS3_SQLITE}" "${ENABLE_SQLITE}") - string(APPEND out "Tap Bridge : ${NS3_TAP}\n") - string(APPEND out "Tap FdNetDevice : ") - check_on_or_off("${NS3_TAP}" "${ENABLE_TAP}") + string(APPEND out "Tap Bridge : ") + check_on_or_off("${ENABLE_TAP}" "${ENABLE_TAP}") + + string(APPEND out "Tap FdNetDevice : ") + check_on_or_off("${ENABLE_TAP}" "${ENABLE_TAPNETDEV}") + + string(APPEND out "Tests : ") + check_on_or_off("${ENABLE_TESTS}" "${ENABLE_TESTS}") - string(APPEND out "Tests : ${TESTS_ENABLED}\n") string(APPEND out "Threading Primitives : ") check_on_or_off("${NS3_PTHREAD}" "${THREADS_ENABLED}") @@ -176,10 +188,16 @@ macro(write_fakewaf_config) string(APPEND out "\n\n") set(really-enabled-modules ${ns3-libs};${ns3-contrib-libs}) - print_formatted_table_with_modules( - "Modules that can be built" "${really-enabled-modules}" "out" - ) - string(APPEND out "\n") + if(${ENABLE_TESTS}) + list(APPEND really-enabled-modules libtest) # test is an object library and + # is treated differently + endif() + if(really-enabled-modules) + print_formatted_table_with_modules( + "Modules configured to be built" "${really-enabled-modules}" "out" + ) + string(APPEND out "\n") + endif() set(disabled-modules) foreach(module ${ns3-all-enabled-modules}) @@ -187,10 +205,13 @@ macro(write_fakewaf_config) list(APPEND disabled-modules ${module}) endif() endforeach() - print_formatted_table_with_modules( - "Modules that cannot be built" "${disabled-modules}" "out" - ) - string(APPEND out "\n") + + if(disabled-modules) + print_formatted_table_with_modules( + "Modules that cannot be built" "${disabled-modules}" "out" + ) + string(APPEND out "\n") + endif() file(WRITE ${PROJECT_BINARY_DIR}/ns3wafconfig.txt ${out}) message(STATUS ${out}) diff --git a/buildsupport/macros_and_definitions.cmake b/buildsupport/macros_and_definitions.cmake index 79c032c18..9b0495bec 100644 --- a/buildsupport/macros_and_definitions.cmake +++ b/buildsupport/macros_and_definitions.cmake @@ -18,11 +18,11 @@ # Export compile time variable setting the directory to the NS3 root folder add_definitions(-DPROJECT_SOURCE_PATH="${PROJECT_SOURCE_DIR}") -# Cache options for INT64X64 -set(INT64X64 "INT128" CACHE STRING "Int64x64 implementation") -set(INT64X64 "CAIRO" CACHE STRING "Int64x64 implementation") -set(INT64X64 "DOUBLE" CACHE STRING "Int64x64 implementation") -set_property(CACHE INT64X64 PROPERTY STRINGS INT128 CAIRO DOUBLE) +# Cache options for NS3_INT64X64 +set(NS3_INT64X64 "INT128" CACHE STRING "Int64x64 implementation") +set(NS3_INT64X64 "CAIRO" CACHE STRING "Int64x64 implementation") +set(NS3_INT64X64 "DOUBLE" CACHE STRING "Int64x64 implementation") +set_property(CACHE NS3_INT64X64 PROPERTY STRINGS INT128 CAIRO DOUBLE) # WSLv1 doesn't support tap features if(EXISTS "/proc/version") @@ -63,36 +63,42 @@ if("${NS3_OUTPUT_DIRECTORY}" STREQUAL "") set(CMAKE_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/build) # default output # folder else() - # Check if we can create that directory - if(NOT (EXISTS ${NS3_OUTPUT_DIRECTORY})) + # Check if NS3_OUTPUT_DIRECTORY is a relative path + set(absolute_ns3_output_directory "${NS3_OUTPUT_DIRECTORY}") + if(NOT IS_ABSOLUTE ${NS3_OUTPUT_DIRECTORY}) + set(absolute_ns3_output_directory + "${PROJECT_SOURCE_DIR}/${NS3_OUTPUT_DIRECTORY}" + ) + endif() + if(NOT (EXISTS ${absolute_ns3_output_directory})) message( STATUS "User-defined output directory \"${NS3_OUTPUT_DIRECTORY}\" doesn't exist. Trying to create it" ) - file(MAKE_DIRECTORY ${NS3_OUTPUT_DIRECTORY}) - if(NOT (EXISTS ${NS3_OUTPUT_DIRECTORY})) + file(MAKE_DIRECTORY ${absolute_ns3_output_directory}) + if(NOT (EXISTS ${absolute_ns3_output_directory})) message( FATAL_ERROR - "User-defined output directory \"${NS3_OUTPUT_DIRECTORY}\" could not be created. " + "User-defined output directory \"${absolute_ns3_output_directory}\" could not be created. " "Try changing the value of NS3_OUTPUT_DIRECTORY" ) endif() # If this directory is not inside the ns-3-dev folder, alert users tests may # break - if(NOT ("${NS3_OUTPUT_DIRECTORY}" MATCHES "${PROJECT_SOURCE_DIR}")) + if(NOT ("${absolute_ns3_output_directory}" MATCHES "${PROJECT_SOURCE_DIR}")) message( WARNING - "User-defined output directory \"${NS3_OUTPUT_DIRECTORY}\" is outside " + "User-defined output directory \"${absolute_ns3_output_directory}\" is outside " " of the ns-3 directory ${PROJECT_SOURCE_DIR}, which will break some tests" ) endif() endif() message( STATUS - "User-defined output directory \"${NS3_OUTPUT_DIRECTORY}\" will be used" + "User-defined output directory \"${absolute_ns3_output_directory}\" will be used" ) - set(CMAKE_OUTPUT_DIRECTORY ${NS3_OUTPUT_DIRECTORY}) + set(CMAKE_OUTPUT_DIRECTORY ${absolute_ns3_output_directory}) endif() set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_OUTPUT_DIRECTORY}/lib) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_OUTPUT_DIRECTORY}/lib) @@ -125,16 +131,40 @@ endif() # fPIC (position-independent code) and fPIE (position-independent executable) set(CMAKE_POSITION_INDEPENDENT_CODE ON) -# Identify compiler +# Identify compiler and check version +set(below_minimum_msg "compiler is below the minimum required version") set(CLANG FALSE) -if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") +if("${CMAKE_CXX_COMPILER_ID}" MATCHES "AppleClang") + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS ${AppleClang_MinVersion}) + message( + FATAL_ERROR + "Apple Clang ${CMAKE_CXX_COMPILER_VERSION} ${below_minimum_msg} ${AppleClang_MinVersion}" + ) + endif() + set(CLANG TRUE) +endif() + +if((NOT CLANG) AND ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")) + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS ${Clang_MinVersion}) + message( + FATAL_ERROR + "Clang ${CMAKE_CXX_COMPILER_VERSION} ${below_minimum_msg} ${Clang_MinVersion}" + ) + endif() set(CLANG TRUE) endif() set(GCC FALSE) if("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS ${GNU_MinVersion}) + message( + FATAL_ERROR + "GNU ${CMAKE_CXX_COMPILER_VERSION} ${below_minimum_msg} ${GNU_MinVersion}" + ) + endif() set(GCC TRUE) endif() +unset(below_minimum_msg) # Set compiler options and get command to force unused function linkage (useful # for libraries) @@ -202,6 +232,11 @@ endmacro() macro(process_options) clear_global_cached_variables() + # make sure to default to debug if no build type is specified + if("${CMAKE_BUILD_TYPE}" STREQUAL "") + set(CMAKE_BUILD_TYPE debug) + endif() + # process debug switch Used in build-profile-test-suite string(TOLOWER ${CMAKE_BUILD_TYPE} cmakeBuildType) set(build_profile "${cmakeBuildType}" CACHE INTERNAL "") @@ -220,16 +255,16 @@ macro(process_options) # Enable examples if activated via command line (NS3_EXAMPLES) or ns3rc config # file - set(EXAMPLES_ENABLED OFF) + set(ENABLE_EXAMPLES OFF) if(${NS3_EXAMPLES} OR ${ns3rc_examples_enabled}) - set(EXAMPLES_ENABLED ON) + set(ENABLE_EXAMPLES ON) endif() # Enable examples if activated via command line (NS3_TESTS) or ns3rc config # file - set(TESTS_ENABLED OFF) + set(ENABLE_TESTS OFF) if(${NS3_TESTS} OR ${ns3rc_tests_enabled}) - set(TESTS_ENABLED ON) + set(ENABLE_TESTS ON) endif() set(profiles_without_suffixes release) @@ -593,10 +628,12 @@ macro(process_options) endif() find_package(Python3 COMPONENTS Interpreter Development) + set(ENABLE_PYTHON_BINDINGS OFF) if(${NS3_PYTHON_BINDINGS}) if(NOT ${Python3_FOUND}) message(FATAL_ERROR "NS3_PYTHON_BINDINGS requires Python3") endif() + set(ENABLE_PYTHON_BINDINGS ON) link_directories(${Python3_LIBRARY_DIRS}) include_directories(${Python3_INCLUDE_DIRS}) set(PYTHONDIR ${Python3_SITELIB}) @@ -615,7 +652,7 @@ macro(process_options) add_custom_target(apiscan-all) endif() - if(${NS3_COVERAGE} AND (NOT ${TESTS_ENABLED} OR NOT ${EXAMPLES_ENABLED})) + if(${NS3_COVERAGE} AND (NOT ${ENABLE_TESTS} OR NOT ${ENABLE_EXAMPLES})) message( FATAL_ERROR "Code coverage requires examples and tests.\n" @@ -623,7 +660,7 @@ macro(process_options) ) endif() - if(${TESTS_ENABLED}) + if(${ENABLE_TESTS}) add_custom_target(all-test-targets) # Create a custom target to run test.py --nowaf Target is also used to @@ -634,7 +671,7 @@ macro(process_options) WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} DEPENDS all-test-targets ) - if(${EXAMPLES_ENABLED}) + if(${ENABLE_EXAMPLES}) include(buildsupport/custom_modules/ns3_coverage.cmake) endif() endif() @@ -719,8 +756,8 @@ macro(process_options) COMMAND ${CMAKE_COMMAND} -E env NS_COMMANDLINE_INTROSPECTION=.. ${Python3_EXECUTABLE} ./test.py --nowaf --constrain=example WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} - DEPENDS all-test-targets # all-test-targets only exists if TESTS_ENABLED - # is set to ON + DEPENDS all-test-targets # all-test-targets only exists if ENABLE_TESTS is + # set to ON ) file( @@ -801,7 +838,7 @@ CommandLine configuration in those files instead. endif() # Process core-config - if(${INT64X64} MATCHES "INT128") + if(${NS3_INT64X64} MATCHES "INT128") include(buildsupport/3rd_party/FindInt128.cmake) find_int128_types() if(UINT128_FOUND) @@ -809,15 +846,15 @@ CommandLine configuration in those files instead. set(INT64X64_USE_128 TRUE) else() message(STATUS "Int128 was not found. Falling back to Cairo.") - set(INT64X64 "CAIRO") + set(NS3_INT64X64 "CAIRO") endif() endif() - if(${INT64X64} MATCHES "CAIRO") + if(${NS3_INT64X64} MATCHES "CAIRO") set(INT64X64_USE_CAIRO TRUE) endif() - if(${INT64X64} MATCHES "DOUBLE") + if(${NS3_INT64X64} MATCHES "DOUBLE") # WSLv1 has a long double issue that will result in a few tests failing # https://github.com/microsoft/WSL/issues/830 include(CheckTypeSize) @@ -829,7 +866,7 @@ CommandLine configuration in those files instead. STATUS "Long double has the wrong size: LD ${SIZEOF_LONG_DOUBLE} vs D ${SIZEOF_DOUBLE}. Falling back to CAIRO." ) - set(INT64X64 "CAIRO") + set(NS3_INT64X64 "CAIRO") else() set(INT64X64_USE_DOUBLE TRUE) endif() @@ -867,18 +904,28 @@ CommandLine configuration in those files instead. endif() # Enable examples as tests suites - if(${EXAMPLES_ENABLED}) + if(${ENABLE_EXAMPLES}) set(NS3_ENABLE_EXAMPLES "1") add_definitions(-DNS3_ENABLE_EXAMPLES -DCMAKE_EXAMPLE_AS_TEST) endif() + set(ENABLE_TAP OFF) + if(${NS3_TAP}) + set(ENABLE_TAP ON) + endif() + + set(ENABLE_EMU OFF) + if(${NS3_EMU}) + set(ENABLE_EMU ON) + endif() + set(PLATFORM_UNSUPPORTED_PRE "Platform doesn't support") set(PLATFORM_UNSUPPORTED_POST "features. Continuing without them.") # Remove from libs_to_build all incompatible libraries or the ones that # dependencies couldn't be installed if(APPLE OR WSLv1) - set(NS3_TAP OFF) - set(NS3_EMU OFF) + set(ENABLE_TAP OFF) + set(ENABLE_EMU OFF) list(REMOVE_ITEM libs_to_build fd-net-device) message( STATUS @@ -894,7 +941,7 @@ CommandLine configuration in those files instead. list(REMOVE_ITEM libs_to_build visualizer) endif() - if(NOT ${NS3_TAP}) + if(NOT ${ENABLE_TAP}) list(REMOVE_ITEM libs_to_build tap-bridge) endif() @@ -1021,7 +1068,7 @@ function(set_runtime_outputdirectory target_name output_directory target_prefix) endforeach(OUTPUTCONFIG CMAKE_CONFIGURATION_TYPES) endif() - if(${TESTS_ENABLED}) + if(${ENABLE_TESTS}) add_dependencies(all-test-targets ${target_prefix}${target_name}) endif() @@ -1120,7 +1167,7 @@ endmacro() function(filter_modules modules_to_filter all_modules_list filter_in) set(new_modules_to_build) - # We are receiveing variable names as arguments, so we have to "dereference" + # We are receiving variable names as arguments, so we have to "dereference" # them first That is why we have the duplicated ${${}} foreach(module ${${all_modules_list}}) if(${filter_in} (${module} IN_LIST ${modules_to_filter})) @@ -1153,6 +1200,8 @@ endfunction() function(filter_libraries cmakelists_contents libraries) string(REGEX MATCHALL "{lib[^}]*[^obj]}" matches "${cmakelists_content}") list(REMOVE_ITEM matches "{libraries_to_link}") + string(REPLACE "{lib\${name" "" matches "${matches}") # special case for + # src/test string(REPLACE "{lib" "" matches "${matches}") string(REPLACE "}" "" matches "${matches}") set(${libraries} ${matches} PARENT_SCOPE) @@ -1183,20 +1232,20 @@ function(recursive_dependency module_name) set(contrib_dependency_visited "${contrib_dependency_visited};${module_name}" CACHE INTERNAL "" ) - set(examples_cmakelist ${contrib_cmakelist}) + set(examples_cmakelists ${contrib_cmakelist}) else() set(dependency_visited "${dependency_visited};${module_name}" CACHE INTERNAL "" ) - set(examples_cmakelist ${src_cmakelist}) + set(examples_cmakelists ${src_cmakelist}) endif() # cmake-format: off # Scan dependencies required by this module examples - #if(${EXAMPLES_ENABLED}) - # string(REPLACE "${module_name}" "${module_name}/examples" examples_cmakelist ${examples_cmakelist}) - # if(EXISTS ${examples_cmakelist}) - # file(READ ${examples_cmakelist} cmakelists_content) + #if(${ENABLE_EXAMPLES}) + # string(REPLACE "${module_name}" "${module_name}/examples" examples_cmakelists ${examples_cmakelists}) + # if(EXISTS ${examples_cmakelists}) + # file(READ ${examples_cmakelists} cmakelists_content) # filter_libraries(${cmakelists_content} example_matches) # endif() #endif() @@ -1218,16 +1267,20 @@ macro(filter_enabled_and_disabled_modules libs_to_build contrib_libs_to_build ) mark_as_advanced(ns3-all-enabled-modules) - # Before filtering, we set a variable with all scanned moduled in the src + # Before filtering, we set a variable with all scanned modules in the src # directory set(scanned_modules ${${libs_to_build}}) + # Ensure enabled and disable modules lists are using semicolons + string(REPLACE "," ";" ${NS3_ENABLED_MODULES} "${${NS3_ENABLED_MODULES}}") + string(REPLACE "," ";" ${NS3_DISABLED_MODULES} "${${NS3_DISABLED_MODULES}}") + # Now that scanning modules finished, we can remove the disabled modules or # replace the modules list with the ones in the enabled list if(${NS3_ENABLED_MODULES} OR ${ns3rc_enabled_modules}) # List of enabled modules passed by the command line overwrites list read # from ns3rc - if(NS3_ENABLED_MODULES) + if(${NS3_ENABLED_MODULES}) set(ns3rc_enabled_modules ${${NS3_ENABLED_MODULES}}) endif() @@ -1235,9 +1288,9 @@ macro(filter_enabled_and_disabled_modules libs_to_build contrib_libs_to_build filter_modules(ns3rc_enabled_modules libs_to_build "") filter_modules(ns3rc_enabled_modules contrib_libs_to_build "") - # Recursively build a list of module dependendencies + # Use recursion to automatically determine dependencies required by the + # manually enabled modules foreach(lib ${${contrib_libs_to_build}}) - message(WARNING "contrib ${lib}") resolve_dependencies(${lib} dependencies contrib_dependencies) list(APPEND ${contrib_libs_to_build} "${contrib_dependencies}") list(APPEND ${libs_to_build} "${dependencies}") @@ -1252,15 +1305,51 @@ macro(filter_enabled_and_disabled_modules libs_to_build contrib_libs_to_build unset(contrib_dependencies) endforeach() - if(${TESTS_ENABLED}) - list(APPEND ${libs_to_build} test) # force enable test module if - # TESTS_ENABLED is enabled + if(core IN_LIST ${libs_to_build}) + list(APPEND ${libs_to_build} test) # include test module endif() endif() - if(NS3_DISABLED_MODULES) - filter_modules(NS3_DISABLED_MODULES libs_to_build "NOT") - filter_modules(NS3_DISABLED_MODULES contrib_libs_to_build "NOT") + if(${NS3_DISABLED_MODULES}) + set(all_libs ${${libs_to_build}};${${contrib_libs_to_build}}) + + # We then use the recursive dependency finding to get all dependencies of + # all modules + foreach(lib ${all_libs}) + resolve_dependencies(${lib} dependencies contrib_dependencies) + set(${lib}_dependencies "${dependencies};${contrib_dependencies}") + unset(dependencies) + unset(contrib_dependencies) + endforeach() + + # Now we can begin removing libraries that require disabled dependencies + set(disabled_libs "${${NS3_DISABLED_MODULES}}") + foreach(libo ${all_libs}) + foreach(lib ${all_libs}) + foreach(disabled_lib ${disabled_libs}) + if(${lib} STREQUAL ${disabled_lib}) + continue() + endif() + if(${disabled_lib} IN_LIST ${lib}_dependencies) + list(APPEND disabled_libs ${lib}) + break() # proceed to the next lib in all_libs + endif() + endforeach() + endforeach() + endforeach() + + # Clean dependencies lists + foreach(lib ${all_libs}) + unset(${lib}_dependencies) + endforeach() + + # We can filter out disabled modules + filter_modules(disabled_libs libs_to_build "NOT") + filter_modules(disabled_libs contrib_libs_to_build "NOT") + + if(core IN_LIST ${libs_to_build}) + list(APPEND ${libs_to_build} test) # include test module + endif() endif() # Older CMake versions require this workaround for empty lists @@ -1300,7 +1389,7 @@ function(parse_ns3rc enabled_modules examples_enabled tests_enabled) set(${enabled_modules}) else() # If modules are listed, remove quotes and replace commas with - # semicollons transforming a string into a cmake list + # semicolons transforming a string into a cmake list string(REPLACE "," ";" ${enabled_modules} "${${enabled_modules}}") string(REPLACE "'" "" ${enabled_modules} "${${enabled_modules}}") string(REPLACE "\"" "" ${enabled_modules} "${${enabled_modules}}") diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index afbf712cc..33ae91343 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,4 +1,4 @@ -if(${EXAMPLES_ENABLED}) +if(${ENABLE_EXAMPLES}) subdirlist(examples_to_build ${CMAKE_CURRENT_SOURCE_DIR}) # Process subdirectories diff --git a/ns3 b/ns3 index 2528e5872..86780e7ac 100755 --- a/ns3 +++ b/ns3 @@ -1,5 +1,6 @@ #! /usr/bin/env python3 +import atexit import argparse import glob import os @@ -8,13 +9,26 @@ import shutil import subprocess import sys -from utils import read_config_file - -out_dir = "" -build_separator = "" ns3_path = os.path.dirname(os.path.abspath(__file__)) +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 + + +# 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: + return + if dry_run: + print("The following commands would be executed:") + elif not run_only: + print("Finished executing the following commands:") + print(print_buffer[1:]) + def on_off_argument(parser, option_name, help_on, help_off=None): parser.add_argument('--enable-%s' % option_name, @@ -79,6 +93,8 @@ def parse_args(argv): parser_configure = on_off_argument(parser_configure, "werror", "Treat compiler warnings as errors", "Treat compiler warnings as warnings") parser_configure = on_off_argument(parser_configure, "documentation", "documentation targets") + parser_configure = on_off_argument(parser_configure, "sanitizers", + "address, memory leaks and undefined behavior sanitizers") parser_configure.add_argument('--enable-modules', help='List of modules to build (e.g. core;network;internet)', @@ -114,24 +130,43 @@ def parse_args(argv): help='Force refresh the CMake cache by deleting' 'the cache and reconfiguring the project', action="store_true", default=None) + parser_configure.add_argument('--prefix', + help='Target output directory to install', + action="store", default=None) + parser_configure.add_argument('--dry-run', + help="Do not execute the commands", + action="store_true", default=None, dest="configure_dry_run") parser_build = sub_parser.add_parser('build', - help='Accepts a list of targets to build, or builds the entire project if no target is given') + help=('Accepts a list of targets to build,', + ' or builds the entire project if no target is given')) parser_build.add_argument('build', help='Build the entire project or the specified target and dependencies', action="store", nargs='*', default=None) + parser_build.add_argument('--dry-run', + help="Do not execute the commands", + action="store_true", default=None, dest="build_dry_run") parser_clean = sub_parser.add_parser('clean', help='Removes files created by waf and ns3') parser_clean.add_argument('clean', nargs="?", action="store", default=True) + parser_clean.add_argument('--dry-run', + help="Do not execute the commands", + action="store_true", default=None, dest="clean_dry_run") + + parser_install = sub_parser.add_parser('install', help='Install ns-3') + parser_install.add_argument('install', + nargs="?", + action="store", default=True) + parser_uninstall = sub_parser.add_parser('uninstall', help='Uninstall ns-3') + parser_uninstall.add_argument('uninstall', + nargs="?", + action="store", default=True) parser.add_argument('--dry-run', help="Do not execute the commands", - action="store_true", default=None) - parser.add_argument('-v', '--verbose', - help="Print underlying commands", - action="store_true", default=None) + action="store_true", default=None, dest="dry_run") parser.add_argument('--check-config', help='Print the current configuration.', action="store_true", default=None) @@ -206,7 +241,14 @@ def parse_args(argv): # action="store_true", default=None, # dest="docset_build") - return parser.parse_args(argv) + args = parser.parse_args(argv) + + # Merge dry_runs + dry_run_args = [(args.__getattribute__(name) if name in args else None) for name in + ["build_dry_run", "clean_dry_run", "configure_dry_run", "dry_run"]] + args.dry_run = dry_run_args.count(True) > 0 + + return args def check_build_profile(output_directory): @@ -226,9 +268,14 @@ def check_build_profile(output_directory): return build_profile, ns3_version, ns3_modules + ns3_modules_tests if ns3_modules else None -def clean_cmake_artifacts(verbose=False, dry_run=False): - if verbose: - print("rm -R %s" % out_dir) +def print_and_buffer(message): + global print_buffer + # print(message) + print_buffer += "\n" + message + + +def clean_cmake_artifacts(dry_run=False): + print_and_buffer("rm -R %s" % os.path.relpath(out_dir, ns3_path)) if not dry_run: shutil.rmtree(out_dir, ignore_errors=True) @@ -236,15 +283,14 @@ def clean_cmake_artifacts(verbose=False, dry_run=False): cmake_cache_files = glob.glob("%s/**/CMakeCache.txt" % ns3_path, recursive=True) for cmake_cache_file in cmake_cache_files: dirname = os.path.dirname(cmake_cache_file) - if verbose: - print("rm -R %s" % dirname) + print_and_buffer("rm -R %s" % os.path.relpath(dirname, ns3_path)) if not dry_run: shutil.rmtree(dirname, ignore_errors=True) if os.path.exists(lock_file): - if verbose: - print("rm %s" % lock_file) - os.remove(lock_file) + print_and_buffer("rm %s" % os.path.relpath(lock_file, ns3_path)) + if not dry_run: + os.remove(lock_file) def search_cmake_cache(build_profile): @@ -313,8 +359,7 @@ def check_config(current_cmake_cache_folder): print(f.read()) -def configure_cmake(cmake, args, current_cmake_cache_folder, current_cmake_generator, output, verbose=False, - dry_run=False): +def configure_cmake(cmake, args, current_cmake_cache_folder, current_cmake_generator, output, dry_run=False): # Aggregate all flags to configure CMake cmake_args = [cmake] @@ -359,7 +404,8 @@ def configure_cmake(cmake, args, current_cmake_cache_folder, current_cmake_gener ("GTK3", "gtk"), ("WARNINGS", "warnings"), ("WARNINGS_AS_ERRORS", "werror"), - ("DOCS", "documentation") + ("DOCS", "documentation"), + ("SANITIZE", "sanitizers") ) for (cmake_flag, option_name) in options: arg = on_off_condition(args, cmake_flag, option_name) @@ -382,6 +428,9 @@ def configure_cmake(cmake, args, current_cmake_cache_folder, current_cmake_gener if args.with_openflow is not None: cmake_args.append("-DNS3_WITH_OPENFLOW=%s" % args.with_openflow) + if args.prefix is not None: + cmake_args.append("-DCMAKE_INSTALL_PREFIX=%s" % args.prefix) + # Build and link visualizer if args.visualize is not None: cmake_args.append("-DNS3_VISUALIZER=%s" % on_off(args.visualize)) @@ -393,21 +442,25 @@ def configure_cmake(cmake, args, current_cmake_cache_folder, current_cmake_gener if args.disable_modules: cmake_args.append("-DNS3_DISABLED_MODULES=%s" % args.disable_modules) - # Try to set specified generator (will probably fail if there is an old cache) - if args.G: - cmake_args.append("-G%s" % args.G) + # Try to set specified generator (will probably fail if there is an old cache) + if "configure" in args and args.G: + cmake_args.append("-G") + cmake_args.append(args.G) + + # Configure cmake + cmake_args.append("..") # for now, assuming the cmake_cache directory is inside the ns-3-dev folder if "configure" not in args: if first_run: print("You need to configure ns-3 first: try ./ns3 configure") exit(0) - # Configure cmake - cmake_args.append("..") # for now, assuming the cmake_cache directory is inside the ns-3-dev folder - # Echo out the configure command - if verbose: - print("cd %s; %s" % (current_cmake_cache_folder, " ".join(cmake_args))) + print_and_buffer("cd %s; %s ; cd %s" % (os.path.relpath(current_cmake_cache_folder, ns3_path), + " ".join(cmake_args), + os.path.relpath(ns3_path, current_cmake_cache_folder) + ) + ) # Run cmake if not dry_run: @@ -430,7 +483,7 @@ def get_program_shortcuts(build_profile, ns3_version): if "pch_exec" in program: continue temp_path = program.split(out_dir_name)[-1].split(os.sep) - # Remove version prefix and buildtype suffix from shortcuts (or keep them too?) + # Remove version prefix and build type suffix from shortcuts (or keep them too?) temp_path[-1] = temp_path[-1].replace("-" + build_profile, "").replace("ns" + ns3_version + "-", "") # Check if there is a .cc file for that specific program @@ -450,7 +503,7 @@ def get_program_shortcuts(build_profile, ns3_version): return ns3_program_map -def cmake_build(current_cmake_cache_folder, output, jobs, target=None, verbose=False, dry_run=False): +def cmake_build(current_cmake_cache_folder, output, jobs, target=None, dry_run=False): # Check CMake version cmake = shutil.which("cmake") if not cmake: @@ -461,8 +514,12 @@ def cmake_build(current_cmake_cache_folder, output, jobs, target=None, verbose=F jobs_part = ("-j %d" % jobs) if version >= "3.12.0" else "" target_part = (" --target %s" % target) if target else "" cmake_build_command = "cmake --build . %s%s" % (jobs_part, target_part) - if verbose: - print("cd %s; %s" % (current_cmake_cache_folder, cmake_build_command)) + + print_and_buffer("cd %s; %s ; cd %s" % (os.path.relpath(current_cmake_cache_folder, ns3_path), + cmake_build_command, + os.path.relpath(ns3_path, current_cmake_cache_folder) + ) + ) if not dry_run: subprocess.run(cmake_build_command.split(), cwd=current_cmake_cache_folder, stdout=output) @@ -474,13 +531,13 @@ def extract_cmakecache_settings(current_cmake_cache_folder): except FileNotFoundError as e: raise e current_settings = re.findall("(NS3_.*):.*=(.*)", contents) # extract NS3 specific settings - current_settings.extend(re.findall("(CMAKE_BUILD_TYPE):.*=(.*)", contents)) # extract buildtype + current_settings.extend(re.findall("(CMAKE_BUILD_TYPE):.*=(.*)", contents)) # extract build type current_settings.extend(re.findall("(CMAKE_GENERATOR):.*=(.*)", contents)) # extract generator return dict(current_settings) -def reconfigure_cmake_to_force_refresh(cmake, current_cmake_cache_folder, output, verbose=False, dry_run=False): +def reconfigure_cmake_to_force_refresh(cmake, current_cmake_cache_folder, output, dry_run=False): import json settings_bak_file = "settings.json" @@ -492,8 +549,8 @@ def reconfigure_cmake_to_force_refresh(cmake, current_cmake_cache_folder, output settings = json.load(f) # Delete cache folder and then recreate it - if verbose: - print("rm -R %s; mkdir %s" % current_cmake_cache_folder * 2) + cache_path = os.path.relpath(current_cmake_cache_folder, ns3_path) + print_and_buffer("rm -R %s; mkdir %s" % (cache_path, cache_path)) if not dry_run: shutil.rmtree(current_cmake_cache_folder) os.mkdir(current_cmake_cache_folder) @@ -510,8 +567,11 @@ def reconfigure_cmake_to_force_refresh(cmake, current_cmake_cache_folder, output cmake_args.append("..") # Echo out the configure command - if verbose: - print("cd %s; %s" % (current_cmake_cache_folder, " ".join(cmake_args))) + print_and_buffer("cd %s; %s ; cd %s" % (os.path.relpath(ns3_path, current_cmake_cache_folder), + " ".join(cmake_args), + os.path.relpath(current_cmake_cache_folder, ns3_path) + ) + ) # Call cmake if not dry_run: @@ -538,7 +598,7 @@ def get_target_to_build(program_path, ns3_version, build_profile): return program_name.split("/")[-1] -def configuration_step(current_cmake_cache_folder, current_cmake_generator, args, run_only, configure_and_run, +def configuration_step(current_cmake_cache_folder, current_cmake_generator, args, configure_and_run, output, dry_run=False): # There are a few cases where we want to reconfigure/refresh the cmake cache # MANUALLY (does require ./ns3 configure) @@ -552,8 +612,7 @@ def configuration_step(current_cmake_cache_folder, current_cmake_generator, args if not current_cmake_cache_folder: current_cmake_cache_folder = os.sep.join([ns3_path, "cmake_cache"]) if not os.path.exists(current_cmake_cache_folder): - if args.verbose: - print("mkdir %s" % current_cmake_cache_folder) + print_and_buffer("mkdir %s" % os.path.relpath(current_cmake_cache_folder, ns3_path)) if not dry_run: os.mkdir(current_cmake_cache_folder) current_cmake_cache_folder = os.path.abspath(current_cmake_cache_folder) @@ -567,7 +626,7 @@ def configuration_step(current_cmake_cache_folder, current_cmake_generator, args # 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 if "configure" in args and args.force_refresh: - reconfigure_cmake_to_force_refresh(cmake, current_cmake_cache_folder, output, args.verbose, dry_run) + reconfigure_cmake_to_force_refresh(cmake, current_cmake_cache_folder, output, dry_run) exit(0) # Call cmake to configure/reconfigure/refresh the project @@ -576,7 +635,6 @@ def configuration_step(current_cmake_cache_folder, current_cmake_generator, args current_cmake_cache_folder, current_cmake_generator, output, - args.verbose, dry_run ) @@ -601,7 +659,6 @@ def build_step(args, cmake_build(current_cmake_cache_folder, jobs=args.jobs, output=output, - verbose=args.verbose, dry_run=args.dry_run ) if "build" in args: @@ -610,7 +667,7 @@ def build_step(args, # If we are building specific targets, we build them one by one if "build" in args: - non_executable_targets = ["doxygen", "doxygen-no-build"] + non_executable_targets = ["doxygen", "doxygen-no-build", "install", "uninstall"] # Build targets in the list for target in args.build: if target in ns3_modules: @@ -621,7 +678,6 @@ def build_step(args, jobs=args.jobs, target=target, output=output, - verbose=args.verbose, dry_run=args.dry_run) # We can also exit earlier in this case exit(0) @@ -632,34 +688,29 @@ def build_step(args, jobs=args.jobs, target=get_target_to_build(target_to_run, ns3_version, build_profile), output=output, - verbose=args.verbose, dry_run=args.dry_run ) def main(): - global out_dir + global out_dir, run_only + # Parse arguments args = parse_args(sys.argv[1:]) + atexit.register(exit_handler, dry_run=args.dry_run) output = subprocess.DEVNULL if args.no_task_lines else None # no arguments were passed, so can't possibly be reconfiguring anything, then we refresh and rebuild if len(sys.argv) == 1: args.build = [] - # If Dry-run, do not execute the commands, only print the underlying commands from --verbose - if args.dry_run: - args.verbose = True - # Read contents from lock (output directory is important) if os.path.exists(lock_file): exec(open(lock_file).read(), globals()) - else: - out_dir = os.sep.join([ns3_path, "build"]) # Clean project if needed if "clean" in args and args.clean: - clean_cmake_artifacts(verbose=args.verbose, dry_run=args.dry_run) + clean_cmake_artifacts(dry_run=args.dry_run) # We end things earlier when cleaning return @@ -671,6 +722,12 @@ def main(): if args.doxygen_no_build: args.build = ['doxygen-no-build'] + # Installation and uninstallation options become cmake targets + if "install" in args: + args.build = ['install'] + if 'uninstall' in args: + args.build = ['uninstall'] + # Get build profile build_profile, ns3_version, ns3_modules = check_build_profile(out_dir) @@ -678,6 +735,8 @@ def main(): run_only = args.run_no_build or args.pyrun_no_build configure_and_run = args.run or args.pyrun target_to_run = None + target_args = [] + current_cmake_cache_folder = None if not args.check and (run_only or configure_and_run): target_to_run = max(args.run_no_build, args.pyrun_no_build, args.run, args.pyrun) if len(target_to_run) > 0: @@ -702,7 +761,6 @@ def main(): first_run, current_cmake_cache_folder = configuration_step(current_cmake_cache_folder, current_cmake_generator, args, - run_only, configure_and_run, output, args.dry_run @@ -749,7 +807,7 @@ def main(): # we can just check if the out_dir matches the base folder from the target_to_run if out_dir.split(os.sep)[-1] == target_to_run.split(os.sep)[0]: target_to_run = target_to_run.replace(out_dir.split(os.sep)[-1] + os.sep, "") - # Waf doesn't add version prefix and build type suffix to scratches, so we remove them + # Waf doesn't add version prefix and build type suffix to the scratches, so we remove them if "scratch" in target_to_run and run_only: target_to_run = target_to_run.replace(os.path.basename(target_to_run), run_only) target_to_run = os.sep.join([out_dir, target_to_run]) @@ -760,15 +818,13 @@ def main(): # Finally, we try to run it if args.check or run_only or configure_and_run: - path_sep = ";" if os.name == "nt" else ":" - paths_to_add = path_sep.join([out_dir, - "%s/lib" % out_dir, - "%s/bindings/python" % out_dir, - ]) - proc_env = {"PATH": os.getenv("PATH") + path_sep + paths_to_add, - "LD_LIBRARY_PATH": paths_to_add, - "PYTHON_PATH": paths_to_add, + libdir = "%s/lib" % out_dir + path_sep = ";" if sys.platform == "win32" else ":" + proc_env = {"PATH": os.getenv("PATH") + path_sep + libdir, + "PYTHON_PATH": "%s/bindings/python" % out_dir, } + if sys.platform != "win32": + proc_env["LD_LIBRARY_PATH"] = libdir # running from ns-3-dev (ns3_path) or cwd working_dir = args.cwd if args.cwd else ns3_path @@ -799,18 +855,24 @@ def main(): program_arguments = [*debugging_software, target_to_run, *target_args] - if args.verbose: + if not run_only or args.dry_run: exported_variables = "export " - for item in proc_env.items(): - exported_variables += "=".join(item) + " " - print("cd %s; %s; %s" % (working_dir, exported_variables, " ".join(program_arguments))) + for (variable, value) in proc_env.items(): + if variable == "PATH": + value = "$PATH" + path_sep + libdir + exported_variables += "%s=%s " % (variable, value) + print_and_buffer("cd %s; %s; %s" % (os.path.relpath(ns3_path, working_dir), + exported_variables, + " ".join(program_arguments) + ) + ) if not args.dry_run: try: subprocess.run(program_arguments, env=proc_env, cwd=working_dir) except KeyboardInterrupt: print("Process was interrupted by the user") - return 0 + return main() diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index f9135cf73..c613f6bcd 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -42,12 +42,12 @@ set(osclock_sources model/unix-system-wall-clock-ms.cc) set(int64x64_sources) set(int64x64_headers) -if(${INT64X64} MATCHES "INT128") +if(${NS3_INT64X64} MATCHES "INT128") set(int64x64_sources model/int64x64-128.cc) set(int64x64_headers model/int64x64-128.h) -elseif(${INT64X64} MATCHES "DOUBLE") +elseif(${NS3_INT64X64} MATCHES "DOUBLE") set(int64x64_headers model/int64x64-double.h) -elseif(${INT64X64} MATCHES "CAIRO") +elseif(${NS3_INT64X64} MATCHES "CAIRO") set(int64x64_sources model/int64x64-cairo.cc) set(int64x64_headers model/int64x64-cairo.h model/cairo-wideint-private.h) endif() @@ -73,7 +73,7 @@ if(${NS3_PTHREAD}) endif() endif() -if(${EXAMPLES_ENABLED}) +if(${ENABLE_EXAMPLES}) set(example_as_test_sources model/example-as-test.cc) set(example_as_test_headers model/example-as-test.h) set(example_as_test_suite test/examples-as-tests-test-suite.cc) diff --git a/src/fd-net-device/CMakeLists.txt b/src/fd-net-device/CMakeLists.txt index 413d7ea7f..6bf211637 100644 --- a/src/fd-net-device/CMakeLists.txt +++ b/src/fd-net-device/CMakeLists.txt @@ -16,12 +16,13 @@ if(PKG_CONFIG_FOUND) endif() mark_as_advanced( - ENABLE_FDNETDEV ENABLE_DPDKDEVNET ENABLE_TAP ENABLE_EMU ENABLE_NETMAP_EMU + ENABLE_FDNETDEV ENABLE_DPDKDEVNET ENABLE_TAPNETDEV ENABLE_EMUNETDEV + ENABLE_NETMAP_EMU ) set(ENABLE_FDNETDEV False CACHE INTERNAL "") set(ENABLE_DPDKDEVNET False CACHE INTERNAL "") -set(ENABLE_TAP False CACHE INTERNAL "") -set(ENABLE_EMU False CACHE INTERNAL "") +set(ENABLE_TAPNETDEV False CACHE INTERNAL "") +set(ENABLE_EMUNETDEV False CACHE INTERNAL "") set(ENABLE_NETMAP_EMU False CACHE INTERNAL "") if(${THREADS_ENABLED} AND HAVE_NET_ETHERNET_H) @@ -34,17 +35,17 @@ if(${THREADS_ENABLED} AND HAVE_NET_ETHERNET_H) if(HAVE_IF_NETS_H AND HAVE_IF_TUN_H AND HAVE_SYS_IOCTL_H - AND ${NS3_TAP} + AND ${ENABLE_TAP} ) - set(ENABLE_TAP True CACHE INTERNAL "") + set(ENABLE_TAPNETDEV True CACHE INTERNAL "") endif() if(HAVE_IF_NETS_H AND HAVE_PACKET_H AND HAVE_SYS_IOCTL_H - AND ${NS3_EMU} + AND ${ENABLE_EMU} ) - set(ENABLE_EMU True CACHE INTERNAL "") + set(ENABLE_EMUNETDEV True CACHE INTERNAL "") add_definitions(-DHAVE_PACKET_H) endif() @@ -57,7 +58,7 @@ endif() if(${ENABLE_FDNETDEV}) set(fd-net-device_creators) - if(${ENABLE_EMU}) + if(${ENABLE_EMUNETDEV}) set(emu_sources helper/emu-fd-net-device-helper.cc) set(emu_headers helper/emu-fd-net-device-helper.h) @@ -75,7 +76,7 @@ if(${ENABLE_FDNETDEV}) list(APPEND fd-net-device_creators raw-sock-creator) endif() - if(${ENABLE_TAP}) + if(${ENABLE_TAPNETDEV}) set(tap_sources helper/tap-fd-net-device-helper.cc) set(tap_headers helper/tap-fd-net-device-helper.h) @@ -94,7 +95,7 @@ if(${ENABLE_FDNETDEV}) list(APPEND fd-net-device_creators tap-device-creator) endif() - if(${ENABLE_TAP} AND ${NS3_PLANETLAB}) # todo: find planetlab libraries + if(${ENABLE_TAPNETDEV} AND ${NS3_PLANETLAB}) # todo: find planetlab libraries set(planetlab_sources helper/planetlab-fd-net-device-helper.cc) set(planetlab_headers helper/planetlab-fd-net-device-helper.h) diff --git a/src/lte/CMakeLists.txt b/src/lte/CMakeLists.txt index 6f22d2373..d904e7bd7 100644 --- a/src/lte/CMakeLists.txt +++ b/src/lte/CMakeLists.txt @@ -1,6 +1,6 @@ set(name lte) -if(${NS3_EMU}) +if(${ENABLE_EMU}) set(emu_sources helper/emu-epc-helper.cc) set(emu_headers helper/emu-epc-helper.h) set(emu_libraries ${libfd-net-device}) diff --git a/src/lte/examples/CMakeLists.txt b/src/lte/examples/CMakeLists.txt index d69c41db5..33c77bd19 100644 --- a/src/lte/examples/CMakeLists.txt +++ b/src/lte/examples/CMakeLists.txt @@ -166,7 +166,7 @@ build_lib_example( "${name}" "${source_files}" "${header_files}" "${libraries_to_link}" ) -if(${NS3_EMU}) +if(${ENABLE_EMU}) set(name lena-simple-epc-emu) set(source_files ${name}.cc) set(header_files) diff --git a/src/mpi/CMakeLists.txt b/src/mpi/CMakeLists.txt index 9a3632755..7aaf6c7fa 100644 --- a/src/mpi/CMakeLists.txt +++ b/src/mpi/CMakeLists.txt @@ -21,7 +21,7 @@ set(header_files model/mpi-interface.h model/mpi-receiver.h set(libraries_to_link ${libcore} ${libnetwork} ${MPI_CXX_LIBRARIES}) set(example_as_test_suite) -if(${EXAMPLES_ENABLED}) +if(${ENABLE_EXAMPLES}) set(example_as_test_suite test/mpi-test-suite.cc) endif() diff --git a/src/sixlowpan/CMakeLists.txt b/src/sixlowpan/CMakeLists.txt index 8784fdde9..b9201c36d 100644 --- a/src/sixlowpan/CMakeLists.txt +++ b/src/sixlowpan/CMakeLists.txt @@ -11,7 +11,7 @@ set(header_files helper/sixlowpan-helper.h model/sixlowpan-header.h set(libraries_to_link ${libinternet} ${libinternet} ${libcore}) set(example_as_test_suite) -if(${EXAMPLES_ENABLED}) +if(${ENABLE_EXAMPLES}) set(example_as_test_suite test/sixlowpan-examples-test-suite.cc) endif() diff --git a/src/tap-bridge/examples/CMakeLists.txt b/src/tap-bridge/examples/CMakeLists.txt index 35fc835c8..90cb68cc0 100644 --- a/src/tap-bridge/examples/CMakeLists.txt +++ b/src/tap-bridge/examples/CMakeLists.txt @@ -1,4 +1,4 @@ -if(${NS3_TAP}) +if(${ENABLE_TAP}) set(name tap-csma) set(source_files ${name}.cc) set(header_files) diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index ac905b10f..467ea6148 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -1,4 +1,4 @@ -if(${TESTS_ENABLED} AND (test IN_LIST libs_to_build)) +if(${ENABLE_TESTS} AND (test IN_LIST libs_to_build)) add_executable(test-runner $ test-runner.cc) if(${NS3_MONOLIB}) diff --git a/utils/create-module.py b/utils/create-module.py index f66511149..dfa05cd48 100755 --- a/utils/create-module.py +++ b/utils/create-module.py @@ -29,7 +29,7 @@ set(libraries_to_link ${{libcore}} ) -if(${{EXAMPLES_ENABLED}}) +if(${{ENABLE_EXAMPLES}}) set(examples_as_tests_sources #test/{MODULE}-examples-test-suite.cc ) diff --git a/utils/tests/gitlab-ci.yml b/utils/tests/gitlab-ci.yml index ad427f01f..24ce845fd 100644 --- a/utils/tests/gitlab-ci.yml +++ b/utils/tests/gitlab-ci.yml @@ -108,8 +108,8 @@ per-commit-compile-optimized: - mkdir -p $CCACHE_BASEDIR_VALUE - export CCACHE_BASEDIR=${PWD} - export CCACHE_DIR=${PWD}/$CCACHE_BASEDIR_VALUE - - ./ns3 --verbose configure -d $MODE -GNinja --enable-examples --enable-tests - - ./ns3 --verbose build + - ./ns3 configure -d $MODE -GNinja --enable-examples --enable-tests + - ./ns3 build - ./test.py --nowaf cache: paths: diff --git a/utils/tests/test-ns3.py b/utils/tests/test-ns3.py index 0ab504222..5cd9a82b9 100644 --- a/utils/tests/test-ns3.py +++ b/utils/tests/test-ns3.py @@ -18,31 +18,31 @@ # # Author: Gabriel Ferreira -import itertools +import glob +import os +import re import shutil -from functools import partial +import subprocess import sys import unittest -import os -import subprocess -import re -import glob +from functools import partial # Get path containing ns3 ns3_path = os.path.dirname(os.path.abspath(os.sep.join([__file__, "../../"]))) ns3_script = os.sep.join([ns3_path, "ns3"]) ns3rc_script = os.sep.join([ns3_path, ".ns3rc"]) -build_status_script = os.sep.join([ns3_path, "build", "build-status.py"]) -c4che_script = os.sep.join([ns3_path, "build", "c4che", "_cache.py"]) +usual_outdir = os.sep.join([ns3_path, "build"]) +usual_build_status_script = os.sep.join([usual_outdir, "build-status.py"]) +usual_c4che_script = os.sep.join([usual_outdir, "c4che", "_cache.py"]) +usual_lib_outdir = os.sep.join([usual_outdir, "lib"]) # Move the current working directory to the ns-3-dev/utils/tests folder os.chdir(os.path.dirname(os.path.abspath(__file__))) # Cmake commands -cmake_refresh_cache_command = "cd {ns3_path}/cmake_cache; /usr/bin/cmake ..".format(ns3_path=ns3_path) -cmake_build_project_command = "cd {ns3_path}/cmake_cache; cmake --build . -j".format(ns3_path=ns3_path) -cmake_build_target_command = partial("cd {ns3_path}/cmake_cache; cmake --build . -j {jobs} --target {target}".format, - ns3_path=ns3_path, +cmake_refresh_cache_command = "/usr/bin/cmake ..".format(ns3_path=ns3_path) +cmake_build_project_command = "cmake --build . -j".format(ns3_path=ns3_path) +cmake_build_target_command = partial("cmake --build . -j {jobs} --target {target}".format, jobs=max(1, os.cpu_count() - 1) ) @@ -52,7 +52,7 @@ def run_ns3(args): # Adapted from https://github.com/metabrainz/picard/blob/master/picard/util/__init__.py -def run_program(program, args, python=False): +def run_program(program, args, python=False, cwd=ns3_path): if type(args) != str: raise Exception("args should be a string") @@ -71,24 +71,31 @@ def run_program(program, args, python=False): stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - timeout=None, - cwd=ns3_path # run process from the ns-3-dev path + cwd=cwd # run process from the ns-3-dev path ) # Return (error code, stdout and stderr) return ret.returncode, ret.stdout.decode(sys.stdout.encoding), ret.stderr.decode(sys.stderr.encoding) -def get_programs_list(): +def get_programs_list(build_status_script_path=usual_build_status_script): values = {} - with open(build_status_script) as f: + with open(build_status_script_path) as f: exec(f.read(), globals(), values) return values["ns3_runnable_programs"] -def read_c4che_entry(entry): +def get_libraries_list(lib_outdir=usual_lib_outdir): + return glob.glob(lib_outdir + '/*', recursive=True) + + +def get_headers_list(outdir=usual_outdir): + return glob.glob(outdir + '/**/*.h', recursive=True) + + +def read_c4che_entry(entry, c4cache_script_path=usual_c4che_script): values = {} - with open(c4che_script) as f: + with open(c4cache_script_path) as f: exec(f.read(), globals(), values) return values[entry] @@ -119,13 +126,13 @@ class NS3RunWafTargets(unittest.TestCase): def test_01_loadExecutables(self): # Check if build-status.py exists, then read to get list of executables - self.assertTrue(os.path.exists(build_status_script)) + self.assertTrue(os.path.exists(usual_build_status_script)) self.ns3_executables = get_programs_list() self.assertGreater(len(self.ns3_executables), 0) def test_02_loadModules(self): # Check if c4che.py exists than read to get the list of enabled modules - self.assertTrue(os.path.exists(c4che_script)) + self.assertTrue(os.path.exists(usual_c4che_script)) self.ns3_modules = get_enabled_modules() self.assertGreater(len(self.ns3_modules), 0) @@ -161,17 +168,12 @@ class NS3CommonSettingsTestCase(unittest.TestCase): self.assertEqual(return_code, 0) self.assertIn("You need to configure ns-3 first: try ./ns3 configure", stdout) - def test_02_Verbose(self): - return_code, stdout, stderr = run_ns3("--verbose") - self.assertEqual(return_code, 0) - self.assertIn("You need to configure ns-3 first: try ./ns3 configure", stdout) - - def test_03_NoTaskLines(self): + def test_02_NoTaskLines(self): return_code, stdout, stderr = run_ns3("--no-task-lines") self.assertEqual(return_code, 0) self.assertIn("You need to configure ns-3 first: try ./ns3 configure", stdout) - def test_04_CheckConfig(self): + def test_03_CheckConfig(self): return_code, stdout, stderr = run_ns3("--check-config") self.assertEqual(return_code, 1) self.assertIn("Project was not configured", stderr) @@ -194,9 +196,9 @@ class NS3ConfigureBuildProfileTestCase(unittest.TestCase): self.assertEqual(return_code, 0) self.assertIn("Built target libcore", stdout) - libcore = glob.glob(os.sep.join([ns3_path, "build", "lib"]) + '/*', recursive=True) - self.assertGreater(len(libcore), 0) - self.assertIn("core-debug", libcore[0]) + libraries = get_libraries_list() + self.assertGreater(len(libraries), 0) + self.assertIn("core-debug", libraries[0]) def test_02_Release(self): return_code, stdout, stderr = run_ns3("configure -d release") @@ -215,9 +217,9 @@ class NS3ConfigureBuildProfileTestCase(unittest.TestCase): self.assertEqual(return_code, 0) self.assertIn("Built target libcore", stdout) - libcore = glob.glob(os.sep.join([ns3_path, "build", "lib"]) + '/*', recursive=True) - self.assertGreater(len(libcore), 0) - self.assertIn("core-optimized", libcore[0]) + libraries = get_libraries_list() + self.assertGreater(len(libraries), 0) + self.assertIn("core-optimized", libraries[0]) def test_04_Typo(self): return_code, stdout, stderr = run_ns3("configure -d Optimized") @@ -253,11 +255,11 @@ class NS3BaseTestCase(unittest.TestCase): self.config_ok(return_code, stdout) # Check if build-status.py exists, then read to get list of executables - self.assertTrue(os.path.exists(build_status_script)) + self.assertTrue(os.path.exists(usual_build_status_script)) self.ns3_executables = get_programs_list() # Check if c4che.py exists than read to get the list of enabled modules - self.assertTrue(os.path.exists(c4che_script)) + self.assertTrue(os.path.exists(usual_c4che_script)) self.ns3_modules = get_enabled_modules() @@ -347,7 +349,43 @@ class NS3ConfigureTestCase(NS3BaseTestCase): # At this point we should have the same amount of modules that we had when we started self.assertEqual(len(get_enabled_modules()), len(self.ns3_modules)) - def test_05_Ns3rc(self): + def test_05_EnableModulesComma(self): + # Try filtering enabled modules to network+wifi and their dependencies + return_code, stdout, stderr = run_ns3("configure --enable-modules='network,wifi'") + self.config_ok(return_code, stdout) + + # At this point we should have fewer modules + enabled_modules = get_enabled_modules() + self.assertLess(len(get_enabled_modules()), len(self.ns3_modules)) + self.assertIn("ns3-network", enabled_modules) + self.assertIn("ns3-wifi", enabled_modules) + + # Try cleaning the list of enabled modules to reset to the normal configuration + return_code, stdout, stderr = run_ns3("configure --enable-modules=''") + self.config_ok(return_code, stdout) + + # At this point we should have the same amount of modules that we had when we started + self.assertEqual(len(get_enabled_modules()), len(self.ns3_modules)) + + def test_06_DisableModulesComma(self): + # Try filtering disabled modules to disable lte and modules that depend on it + return_code, stdout, stderr = run_ns3("configure --disable-modules='lte,mpi'") + 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) + + # Try cleaning the list of enabled modules to reset to the normal configuration + return_code, stdout, stderr = run_ns3("configure --disable-modules=''") + self.config_ok(return_code, stdout) + + # At this point we should have the same amount of modules that we had when we started + self.assertEqual(len(get_enabled_modules()), len(self.ns3_modules)) + + def test_07_Ns3rc(self): ns3rc_template = "# ! /usr/bin/env python\ \ # A list of the modules that will be enabled when ns-3 is run.\ @@ -376,7 +414,7 @@ class NS3ConfigureTestCase(NS3BaseTestCase): self.assertLess(len(get_enabled_modules()), len(self.ns3_modules)) self.assertIn("ns3-lte", enabled_modules) self.assertTrue(get_test_enabled()) - self.assertEqual(len(get_programs_list()) + 1, len(self.ns3_executables)) # the +1 is due to test-runner + self.assertEqual(len(get_programs_list()), len(self.ns3_executables)) # Replace the ns3rc file with open(ns3rc_script, "w") as f: @@ -405,9 +443,55 @@ class NS3ConfigureTestCase(NS3BaseTestCase): self.assertFalse(get_test_enabled()) self.assertEqual(len(get_programs_list()), len(self.ns3_executables)) - def test_06_DryRun(self): - # todo: collect commands from CMake and test them - pass + def test_08_DryRun(self): + run_ns3("clean") + + # Try dry-run before and after the positional commands (outputs should match) + for positional_command in ["configure", "build", "clean"]: + return_code, stdout, stderr = run_ns3("--dry-run %s" % positional_command) + return_code1, stdout1, stderr1 = run_ns3("%s --dry-run" % positional_command) + + self.assertEqual(return_code, return_code1) + self.assertEqual(stdout, stdout1) + self.assertEqual(stderr, stderr1) + + # Build target before using below + run_ns3("configure -d release") + run_ns3("build scratch-simulator") + + # 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_code2, stdout2, stderr2 = run_ns3("--dry-run --run-no-build scratch-simulator") + return_code3, stdout3, stderr3 = run_ns3("--run-no-build scratch-simulator") + + # Return code and stderr should be the same for all of them + self.assertEqual(sum([return_code0, return_code1, return_code2, return_code3]), 0) + self.assertEqual([stderr0, stderr1, stderr2, stderr3], [""]*4) + + scratch_path = None + for program in get_programs_list(): + if "scratch-simulator" in program and "subdir" not in program: + scratch_path = program + break + + # Scratches currently have a 'scratch_' prefix in their CMake targets + # Case 0: dry-run + run (should print commands to refresh cache, build target and then run) + self.assertIn(cmake_refresh_cache_command, stdout0) + self.assertIn(cmake_build_target_command(target="scratch_scratch-simulator"), stdout0) + self.assertIn(scratch_path, stdout0) + + # Case 1: run (should print all the commands of case 1 plus CMake output from build) + self.assertIn(cmake_refresh_cache_command, stdout1) + self.assertIn(cmake_build_target_command(target="scratch_scratch-simulator"), stdout1) + self.assertIn("Built target", stdout1) + self.assertIn(scratch_path, stdout1) + + # Case 2: dry-run + run-no-build (should print commands to run the target) + self.assertIn(scratch_path, stdout2) + + # Case 3: run-no-build (should print the target output only) + self.assertEqual("", stdout3) class NS3BuildBaseTestCase(NS3BaseTestCase): @@ -419,6 +503,8 @@ class NS3BuildBaseTestCase(NS3BaseTestCase): NS3BaseTestCase.cleaned_once = False super().setUp() + self.ns3_libraries = get_libraries_list() + def test_01_BuildExistingTargets(self): return_code, stdout, stderr = run_ns3("build core") self.assertEqual(return_code, 0) @@ -435,28 +521,219 @@ class NS3BuildBaseTestCase(NS3BaseTestCase): self.assertIn("Built target", stdout) for program in get_programs_list(): self.assertTrue(os.path.exists(program)) - - def test_04_BuildProjectVerbose(self): - return_code, stdout, stderr = run_ns3("--verbose build") - self.assertEqual(return_code, 0) - self.assertIn("Built target", stdout) - for program in get_programs_list(): - self.assertTrue(os.path.exists(program)) self.assertIn(cmake_refresh_cache_command, stdout) self.assertIn(cmake_build_project_command, stdout) - def test_05_BuildProjectNoTaskLines(self): + def test_04_BuildProjectNoTaskLines(self): return_code, stdout, stderr = run_ns3("--no-task-lines build") self.assertEqual(return_code, 0) - self.assertEqual(len(stderr), 0) - self.assertEqual(len(stdout), 0) - - def test_06_BuildProjectVerboseAndNoTaskLines(self): - return_code, stdout, stderr = run_ns3("--no-task-lines --verbose build") - self.assertEqual(return_code, 0) self.assertIn(cmake_refresh_cache_command, stdout) self.assertIn(cmake_build_project_command, stdout) + def test_05_TestVersionFile(self): + version_file = os.sep.join([ns3_path, "VERSION"]) + with open(version_file, "w") as f: + f.write("3-00\n") + + # Reconfigure + return_code, stdout, stderr = run_ns3("configure") + self.config_ok(return_code, stdout) + + # Build + return_code, stdout, stderr = run_ns3("build") + self.assertEqual(return_code, 0) + self.assertIn("Built target", stdout) + + # Programs with new versions + new_programs = get_programs_list() + + # Check if they exist + for program in new_programs: + self.assertTrue(os.path.exists(program)) + + # Check if we still have the same number of binaries + self.assertEqual(len(new_programs), len(self.ns3_executables)) + + # Check if versions changed from 3-dev to 3-00 + libraries = get_libraries_list() + new_libraries = list(set(libraries).difference(set(self.ns3_libraries))) + self.assertEqual(len(new_libraries), len(self.ns3_libraries)) + for library in new_libraries: + self.assertNotIn("libns3-dev", library) + self.assertIn("libns3-00", library) + self.assertTrue(os.path.exists(library)) + + # Restore version file + with open(version_file, "w") as f: + f.write("3-dev\n") + + # Reset flag to let it clean the build + NS3BuildBaseTestCase.cleaned_once = False + + def test_06_OutputDirectory(self): + # Re-build to return to the original state + return_code, stdout, stderr = run_ns3("build") + self.config_ok(return_code, stdout) + self.ns3_libraries = get_libraries_list() + self.ns3_executables = get_programs_list() + + # Delete built programs and libraries to check if they were restored later + for program in self.ns3_executables: + os.remove(program) + for library in self.ns3_libraries: + os.remove(library) + + # Reconfigure setting the output folder to ns-3-dev/build/release (both as an absolute path or relative) + absolute_path = os.sep.join([ns3_path, "build", "release"]) + relative_path = os.sep.join(["build", "release"]) + for different_out_dir in [absolute_path, relative_path]: + return_code, stdout, stderr = run_ns3("configure --out=%s" % different_out_dir) + self.config_ok(return_code, stdout) + self.assertIn("Build directory : %s" % absolute_path, stdout) + + # Build + return_code, stdout, stderr = run_ns3("build") + self.config_ok(return_code, stdout) + + # Check if we have the same number of binaries and that they were built correctly + new_programs = get_programs_list(os.sep.join([absolute_path, "build-status.py"])) + self.assertEqual(len(new_programs), len(self.ns3_executables)) + for program in new_programs: + self.assertTrue(os.path.exists(program)) + + # Check if we have the same number of libraries and that they were built correctly + libraries = get_libraries_list(os.sep.join([absolute_path, "lib"])) + new_libraries = list(set(libraries).difference(set(self.ns3_libraries))) + self.assertEqual(len(new_libraries), len(self.ns3_libraries)) + for library in new_libraries: + self.assertTrue(os.path.exists(library)) + + # Remove files in the different output dir + shutil.rmtree(absolute_path) + + # Restore original output directory + return_code, stdout, stderr = run_ns3("configure --out=''") + self.config_ok(return_code, stdout) + self.assertIn("Build directory : %s" % usual_outdir, stdout) + + # Try re-building + return_code, stdout, stderr = run_ns3("build") + self.config_ok(return_code, stdout) + + # Check if we have the same binaries we had at the beginning + new_programs = get_programs_list() + self.assertEqual(len(new_programs), len(self.ns3_executables)) + for program in new_programs: + self.assertTrue(os.path.exists(program)) + + # Check if we have the same libraries we had at the beginning + libraries = get_libraries_list() + self.assertEqual(len(libraries), len(self.ns3_libraries)) + for library in libraries: + self.assertTrue(os.path.exists(library)) + + def test_07_InstallationAndUninstallation(self): + # Remove existing libraries from the previous step + libraries = get_libraries_list() + for library in libraries: + os.remove(library) + + # 3-dev version format is not supported by CMake, so we use 3.01 + version_file = os.sep.join([ns3_path, "VERSION"]) + with open(version_file, "w") as f: + f.write("3-01\n") + + # Reconfigure setting the installation folder to ns-3-dev/build/install + install_prefix = os.sep.join([ns3_path, "build", "install"]) + return_code, stdout, stderr = run_ns3("configure --prefix=%s" % install_prefix) + self.config_ok(return_code, stdout) + + # Build + return_code, stdout, stderr = run_ns3("build") + self.config_ok(return_code, stdout) + libraries = get_libraries_list() + headers = get_headers_list() + + # Install + return_code, stdout, stderr = run_ns3("install") + self.config_ok(return_code, stdout) + + # Find out if libraries were installed to lib or lib64 (Fedora thing) + lib64 = os.path.exists(os.sep.join([install_prefix, "lib64"])) + installed_libdir = os.sep.join([install_prefix, ("lib64" if lib64 else "lib")]) + + # Make sure all libraries were installed + installed_libraries = get_libraries_list(installed_libdir) + installed_libraries_list = ";".join(installed_libraries) + for library in libraries: + library_name = os.path.basename(library) + self.assertIn(library_name, installed_libraries_list) + + # Make sure all headers were installed + installed_headers = get_headers_list(install_prefix) + missing_headers = list(set([os.path.basename(x) for x in headers]) + - (set([os.path.basename(x) for x in installed_headers])) + ) + self.assertEqual(len(missing_headers), 0) + + # Now create a test CMake project and try to find_package ns-3 + test_main_file = os.sep.join([install_prefix, "main.cpp"]) + with open(test_main_file, "w") as f: + f.write("# include \nint main() { return 0; }\n") + + # We try to use this library without specifying a version, + # 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) + + 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) + + # Configure the test project and build + 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) + 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) + + return_code, stdout, stderr = run_program("cmake", "--build .", cwd=install_prefix) + + if version == "3.00": + self.assertEqual(return_code, 2) + self.assertIn("cannot", stderr) + else: + self.assertEqual(return_code, 0) + self.assertIn("Built target", stdout) + + # Uninstall + return_code, stdout, stderr = run_ns3("uninstall") + self.config_ok(return_code, stdout) + self.assertIn("Built target uninstall", stdout) + + # Restore 3-dev version file + with open(version_file, "w") as f: + f.write("3-dev\n") + + # Reset flag to let it clean the build + NS3BuildBaseTestCase.cleaned_once = False + class NS3ExpectedUseTestCase(NS3BaseTestCase): cleaned_once = False @@ -472,27 +749,27 @@ class NS3ExpectedUseTestCase(NS3BaseTestCase): self.config_ok(return_code, stdout) # Check if build-status.py exists, then read to get list of executables - self.assertTrue(os.path.exists(build_status_script)) + self.assertTrue(os.path.exists(usual_build_status_script)) self.ns3_executables = get_programs_list() # Check if c4che.py exists than read to get the list of enabled modules - self.assertTrue(os.path.exists(c4che_script)) + self.assertTrue(os.path.exists(usual_c4che_script)) self.ns3_modules = get_enabled_modules() - def test_01_BuildProjectVerbose(self): - return_code, stdout, stderr = run_ns3("--verbose build") + def test_01_BuildProject(self): + return_code, stdout, stderr = run_ns3("build") self.assertEqual(return_code, 0) self.assertIn("Built target", stdout) for program in get_programs_list(): self.assertTrue(os.path.exists(program)) - libraries = ";".join(glob.glob(os.sep.join([ns3_path, "build", "lib"]) + '/*', recursive=True)) + libraries = get_libraries_list() for module in get_enabled_modules(): - self.assertIn(module.replace("ns3-", ""), libraries) + self.assertIn(module.replace("ns3-", ""), ";".join(libraries)) self.assertIn(cmake_refresh_cache_command, stdout) self.assertIn(cmake_build_project_command, stdout) def test_02_BuildAndRunExistingExecutableTarget(self): - return_code, stdout, stderr = run_ns3('--verbose --run "test-runner --list"') + return_code, stdout, stderr = run_ns3('--run "test-runner --list"') self.assertEqual(return_code, 0) self.assertIn("Built target test-runner", stdout) self.assertIn(cmake_refresh_cache_command, stdout) @@ -509,21 +786,19 @@ class NS3ExpectedUseTestCase(NS3BaseTestCase): self.assertIn("Couldn't find the specified program: nonsense", stderr) def test_05_RunNoBuildExistingExecutableTarget(self): - return_code, stdout, stderr = run_ns3('--verbose --run-no-build "test-runner --list"') + return_code, stdout, stderr = run_ns3('--run-no-build "test-runner --list"') self.assertEqual(return_code, 0) self.assertNotIn("Built target test-runner", stdout) self.assertNotIn(cmake_refresh_cache_command, stdout) self.assertNotIn(cmake_build_target_command(target="test-runner"), stdout) - self.assertIn("test-runner", stdout) - self.assertIn("--list", stdout) def test_06_RunNoBuildExistingLibraryTarget(self): - return_code, stdout, stderr = run_ns3("--verbose --run-no-build core") # this should not work + return_code, stdout, stderr = run_ns3("--run-no-build core") # this should not work self.assertEqual(return_code, 1) self.assertIn("Couldn't find the specified program: core", stderr) def test_07_RunNoBuildNonExistingExecutableTarget(self): - return_code, stdout, stderr = run_ns3("--verbose --run-no-build nonsense") # this should not work + return_code, stdout, stderr = run_ns3("--run-no-build nonsense") # this should not work self.assertEqual(return_code, 1) self.assertIn("Couldn't find the specified program: nonsense", stderr) @@ -540,14 +815,14 @@ class NS3ExpectedUseTestCase(NS3BaseTestCase): self.assertIn("Memcheck", stderr) def test_10_DoxygenWithBuild(self): - return_code, stdout, stderr = run_ns3("--verbose --doxygen") + return_code, stdout, stderr = run_ns3("--doxygen") self.assertEqual(return_code, 0) self.assertIn(cmake_refresh_cache_command, stdout) self.assertIn(cmake_build_target_command(target="doxygen"), stdout) self.assertIn("Built target doxygen", stdout) def test_11_DoxygenWithoutBuild(self): - return_code, stdout, stderr = run_ns3("--verbose --doxygen-no-build") + return_code, stdout, stderr = run_ns3("--doxygen-no-build") self.assertEqual(return_code, 0) self.assertIn(cmake_refresh_cache_command, stdout) self.assertIn(cmake_build_target_command(target="doxygen-no-build"), stdout)