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
This commit is contained in:
Gabriel Ferreira
2022-01-23 17:30:08 -03:00
parent f78ca76519
commit bbe2128abe
13 changed files with 416 additions and 146 deletions

View File

@@ -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

View File

@@ -15,6 +15,75 @@
#
# Author: Gabriel Ferreira <gabrielcarvfer@gmail.com>
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

View File

@@ -16,13 +16,14 @@
# Author: Gabriel Ferreira <gabrielcarvfer@gmail.com>
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.")

View File

@@ -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

View File

@@ -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.")

View File

@@ -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 <simclick.h> should work
get_filename_component(header_include_dir2 ${header_include_dir} DIRECTORY
)# e.g. include/(click) -> #include <click/simclick.h> 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)

View File

@@ -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@

66
ns3
View File

@@ -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:

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)
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 ""
)
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)
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
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)
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
cmake = shutil.which("cmake")
return_code, stdout, stderr = run_program(cmake,
"-DCMAKE_BUILD_TYPE=debug .",
cwd=install_prefix)
# 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)
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)
# 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)