diff --git a/buildsupport/custom_modules/ns3_cmake_package.cmake b/buildsupport/custom_modules/ns3_cmake_package.cmake index f1142d29c..96ec775ee 100644 --- a/buildsupport/custom_modules/ns3_cmake_package.cmake +++ b/buildsupport/custom_modules/ns3_cmake_package.cmake @@ -73,6 +73,18 @@ function(pkgconfig_module libname) "${ns3-libs};${ns3-contrib-libs}" ) + set(pkgconfig_interface_include_directories) + if(${NS3_REEXPORT_THIRD_PARTY_LIBRARIES}) + get_target_includes(${libname} pkgconfig_interface_include_directories) + string(REPLACE "-I${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/include" "" + pkgconfig_interface_include_directories + "${pkgconfig_interface_include_directories}" + ) + string(REPLACE ";-I" " -I" pkgconfig_interface_include_directories + "${pkgconfig_interface_include_directories}" + ) + endif() + # Configure pkgconfig file for the module using pkgconfig variables set(pkgconfig_file ${CMAKE_BINARY_DIR}/pkgconfig/ns3-${module_name}.pc) configure_file( diff --git a/buildsupport/custom_modules/ns3_module_macros.cmake b/buildsupport/custom_modules/ns3_module_macros.cmake index fae3c2e95..5a4cb496f 100644 --- a/buildsupport/custom_modules/ns3_module_macros.cmake +++ b/buildsupport/custom_modules/ns3_module_macros.cmake @@ -118,18 +118,43 @@ macro( unset(module_name) endforeach() - # ns-3 libraries are linked publicly, to make sure other modules can find each - # other without being directly linked - target_link_libraries( - ${lib${libname}} PUBLIC ${LIB_AS_NEEDED_PRE} "${ns_libraries_to_link}" - ${LIB_AS_NEEDED_POST} - ) + if(NOT ${NS3_REEXPORT_THIRD_PARTY_LIBRARIES}) + # ns-3 libraries are linked publicly, to make sure other modules can find + # each other without being directly linked + set(exported_libraries PUBLIC ${LIB_AS_NEEDED_PRE} ${ns_libraries_to_link} + ${LIB_AS_NEEDED_POST} + ) + + # non-ns-3 libraries are linked privately, not propagating unnecessary + # libraries such as pthread, librt, etc + set(private_libraries PRIVATE ${LIB_AS_NEEDED_PRE} + ${non_ns_libraries_to_link} ${LIB_AS_NEEDED_POST} + ) + + # we don't re-export included libraries from 3rd-party modules + set(exported_include_directories) + else() + # we export everything by default when NS3_REEXPORT_THIRD_PARTY_LIBRARIES=ON + set(exported_libraries PUBLIC ${LIB_AS_NEEDED_PRE} ${ns_libraries_to_link} + ${non_ns_libraries_to_link} ${LIB_AS_NEEDED_POST} + ) + set(private_libraries) + + # with NS3_REEXPORT_THIRD_PARTY_LIBRARIES, we export all 3rd-party library + # include directories, allowing consumers of this module to include and link + # the 3rd-party code with no additional setup + get_target_includes(${lib${libname}} exported_include_directories) + string(REPLACE "-I" "" exported_include_directories + "${exported_include_directories}" + ) + string(REPLACE "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/include" "" + exported_include_directories + "${exported_include_directories}" + ) + endif() - # non-ns-3 libraries are linked privately, not propagating unnecessary - # libraries such as pthread, librt, etc target_link_libraries( - ${lib${libname}} PRIVATE ${LIB_AS_NEEDED_PRE} "${non_ns_libraries_to_link}" - ${LIB_AS_NEEDED_POST} + ${lib${libname}} ${exported_libraries} ${private_libraries} ) # set output name of library @@ -145,6 +170,7 @@ macro( target_include_directories( ${lib${libname}} PUBLIC $ $ + INTERFACE ${exported_include_directories} ) set(ns3-external-libs "${non_ns_libraries_to_link};${ns3-external-libs}" @@ -236,7 +262,7 @@ macro( endif() # Add target to scan python bindings - if(${NS3_SCAN_PYTHON_BINDINGS} + if(${ENABLE_SCAN_PYTHON_BINDINGS} AND EXISTS ${CMAKE_HEADER_OUTPUT_DIRECTORY}/${libname}-module.h ) set(bindings_output_folder @@ -266,17 +292,7 @@ macro( ) # API scan needs the include directories to find a few headers (e.g. mpi.h) - get_target_property(include_dirs ${lib${libname}} INCLUDE_DIRECTORIES) - set(modulegen_include_dirs) - foreach(include_dir ${include_dirs}) - if(include_dir MATCHES "<") - # Skip CMake build and install interface includes - continue() - else() - # Append the include directory to a list - set(modulegen_include_dirs ${modulegen_include_dirs} -I${include_dir}) - endif() - endforeach() + get_target_includes(${lib${libname}} modulegen_include_dirs) set(module_to_generate_api ${module_api_ILP32}) set(LP64toILP32) @@ -304,7 +320,9 @@ macro( # Build pybindings if requested and if bindings subfolder exists in # NS3/src/libname - if(${NS3_PYTHON_BINDINGS} AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/bindings") + if(${ENABLE_PYTHON_BINDINGS} AND EXISTS + "${CMAKE_CURRENT_SOURCE_DIR}/bindings" + ) set(bindings_output_folder ${CMAKE_OUTPUT_DIRECTORY}/${folder}/${libname}/bindings ) @@ -354,7 +372,7 @@ macro( message( FATAL_ERROR "Something went wrong during processing of the python bindings of module ${libname}." - " Make sure the correct versions of Pybindgen and Pygccxml are installed (use the pip ones)." + " Make sure you have the latest version of Pybindgen." ) if(EXISTS ${module_src}) file(REMOVE ${module_src}) @@ -407,8 +425,7 @@ macro( ${bindings-name} PROPERTIES OUTPUT_NAME ${prefix}${libname_sub} PREFIX "" - ${suffix} - LIBRARY_OUTPUT_DIRECTORY + ${suffix} LIBRARY_OUTPUT_DIRECTORY ${CMAKE_OUTPUT_DIRECTORY}/bindings/python/ns ) diff --git a/buildsupport/macros_and_definitions.cmake b/buildsupport/macros_and_definitions.cmake index c9417dcfd..050604a88 100644 --- a/buildsupport/macros_and_definitions.cmake +++ b/buildsupport/macros_and_definitions.cmake @@ -24,8 +24,16 @@ 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) -# Purposefully hidden option since we can't really do that safely from the CMake -# side +# Purposefully hidden options: + +# for ease of use, export all libraries and include directories to ns-3 module +# consumers by default +mark_as_advanced(NS3_REEXPORT_THIRD_PARTY_LIBRARIES) +option(NS3_REEXPORT_THIRD_PARTY_LIBRARIES "Export all third-party libraries +and include directories to ns-3 module consumers" ON +) + +# since we can't really do that safely from the CMake side mark_as_advanced(NS3_ENABLE_SUDO) option(NS3_ENABLE_SUDO "Set executables ownership to root and enable the SUID flag" OFF @@ -619,37 +627,69 @@ macro(process_options) find_package(Python COMPONENTS Interpreter Development QUIET) # Check if python3 was found, and mark as not found if python2 is found - if(${Python_FOUND} AND (${Python_VERSION_MAJOR} LESS 3)) - set(Python_FOUND FALSE) - message( - STATUS: - "bindings: an incompatible version of Python was found, bindings will be disabled" - ) + if(${Python_FOUND}) + if(${Python_VERSION_MAJOR} LESS 3) + set(Python_FOUND FALSE) + message( + STATUS + "Python: an incompatible version of Python was found, python bindings will be disabled" + ) + endif() endif() set(ENABLE_PYTHON_BINDINGS OFF) if(${NS3_PYTHON_BINDINGS}) if(NOT ${Python_FOUND}) - message(FATAL_ERROR "NS3_PYTHON_BINDINGS requires Python3") - endif() - set(ENABLE_PYTHON_BINDINGS ON) + message( + STATUS + "Bindings: python bindings require Python, but it could not be found" + ) + else() + check_python_packages("pybindgen" missing_packages) + if(missing_packages) + message( + STATUS + "Bindings: python bindings disabled due to the following missing dependencies: ${missing_packages}" + ) + else() + set(ENABLE_PYTHON_BINDINGS ON) - set(destination_dir ${CMAKE_OUTPUT_DIRECTORY}/bindings/python/ns) - configure_file( - bindings/python/ns__init__.py ${destination_dir}/__init__.py COPYONLY - ) + set(destination_dir ${CMAKE_OUTPUT_DIRECTORY}/bindings/python/ns) + configure_file( + bindings/python/ns__init__.py ${destination_dir}/__init__.py COPYONLY + ) + endif() + endif() endif() + set(ENABLE_SCAN_PYTHON_BINDINGS OFF) if(${NS3_SCAN_PYTHON_BINDINGS}) - # empty scan target that will depend on other module scan targets to scan - # all of them - add_custom_target(apiscan-all) + if(NOT ${Python_FOUND}) + message( + STATUS + "Bindings: scanning python bindings require Python, but it could not be found" + ) + else() + # Check if pybindgen, pygccxml and cxxfilt are installed + check_python_packages("pybindgen;pygccxml;cxxfilt" missing_packages) + if(missing_packages) + message( + STATUS + "Bindings: scanning of python bindings disabled due to the following missing dependencies: ${missing_packages}" + ) + else() + set(ENABLE_SCAN_PYTHON_BINDINGS ON) + # empty scan target that will depend on other module scan targets to + # scan all of them + add_custom_target(apiscan-all) + endif() + endif() endif() set(ENABLE_VISUALIZER FALSE) if(${NS3_VISUALIZER}) if((NOT ${ENABLE_PYTHON_BINDINGS}) OR (NOT ${Python_FOUND})) - message(STATUS "Visualizer requires NS3_PYTHON_BINDINGS and Python3") + message(STATUS "Visualizer requires Python bindings") else() set(ENABLE_VISUALIZER TRUE) endif() @@ -1459,7 +1499,7 @@ function(find_external_library_header_and_library name header_name library_name endfunction() function(write_header_to_modules_map) - if(${NS3_SCAN_PYTHON_BINDINGS}) + if(${ENABLE_SCAN_PYTHON_BINDINGS}) set(header_map ${ns3-headers-to-module-map}) # Trim last comma @@ -1472,6 +1512,35 @@ function(write_header_to_modules_map) endif() endfunction() +function(get_target_includes target output) + set(include_directories) + get_target_property(include_dirs ${target} INCLUDE_DIRECTORIES) + foreach(include_dir ${include_dirs}) + if(include_dir MATCHES "<") + # Skip CMake build and install interface includes + continue() + else() + # Append the include directory to a list + set(include_directories ${include_directories} -I${include_dir}) + endif() + endforeach() + set(${output} ${include_directories} PARENT_SCOPE) +endfunction() + +function(check_python_packages packages missing_packages) + set(missing) + foreach(package ${packages}) + execute_process( + COMMAND ${Python_EXECUTABLE} -c "import ${package}" + RESULT_VARIABLE return_code OUTPUT_QUIET ERROR_QUIET + ) + if(NOT (${return_code} EQUAL 0)) + list(APPEND missing ${package}) + endif() + endforeach() + set(${missing_packages} "${missing}" PARENT_SCOPE) +endfunction() + # Waf workaround scripts include(buildsupport/custom_modules/waf_workaround_c4cache.cmake) include(buildsupport/custom_modules/waf_workaround_buildstatus.cmake) diff --git a/buildsupport/pkgconfig_template.pc.in b/buildsupport/pkgconfig_template.pc.in index dda4eea79..079dcbe57 100644 --- a/buildsupport/pkgconfig_template.pc.in +++ b/buildsupport/pkgconfig_template.pc.in @@ -8,6 +8,6 @@ URL: @CMAKE_PROJECT_HOMEPAGE_URL@ Version: @ns3_version@ Requires: @pkgconfig_public_required@ Requires.private: @pkgconfig_private_required@ -Cflags: -I"${includedir}" +Cflags: -I"${includedir}" @pkgconfig_interface_include_directories@ Libs: -L"${libdir}" -l@pkgconfig_target_lib@ @pkgconfig_public_libs@ Libs.private: -L"${libdir}" @pkgconfig_private_libs@ \ No newline at end of file diff --git a/ns3 b/ns3 index 60c201bed..f23a6bff6 100755 --- a/ns3 +++ b/ns3 @@ -279,24 +279,24 @@ def parse_args(argv): return args -def check_build_profile(output_directory): +def check_c4che_data(output_directory): # Check the c4cache for the build type (in case there are multiple cmake cache folders c4che_path = os.sep.join([output_directory, "c4che", "_cache.py"]) - build_profile = None - ns3_version = None - ns3_modules = None + ns3_modules_tests = [] ns3_modules_apiscan = [] ns3_modules_bindings = [] - enable_sudo = False + ns3_modules = None + + c4che_info = {"BUILD_PROFILE" : None, + "VERSION" : None, + "ENABLE_EXAMPLES" : False, + "ENABLE_SUDO" : False, + "ENABLE_TESTS" : False, + } if output_directory and os.path.exists(c4che_path): - c4che_info = {} exec(open(c4che_path).read(), globals(), c4che_info) - build_profile = c4che_info["BUILD_PROFILE"] - ns3_version = c4che_info["VERSION"] ns3_modules = c4che_info["NS3_ENABLED_MODULES"] - if "ENABLE_SUDO" in c4che_info: - enable_sudo = c4che_info["ENABLE_SUDO"] if ns3_modules: if c4che_info["ENABLE_TESTS"]: ns3_modules_tests = [x + "-test" for x in ns3_modules] @@ -305,7 +305,7 @@ def check_build_profile(output_directory): if "ENABLE_SCAN_PYTHON_BINDINGS" in c4che_info and c4che_info["ENABLE_SCAN_PYTHON_BINDINGS"]: ns3_modules_apiscan = [x + "-apiscan" for x in ns3_modules] ns3_modules = ns3_modules + ns3_modules_tests + ns3_modules_apiscan + ns3_modules_bindings - return build_profile, ns3_version, ns3_modules, enable_sudo + return c4che_info, ns3_modules def print_and_buffer(message): @@ -535,10 +535,13 @@ def get_program_shortcuts(build_profile, ns3_version): # We can now build a map to simplify things for users (at this point we could remove versioning prefix/suffix) ns3_program_map = {} + longest_shortcut_map = {} + for program in programs_dict["ns3_runnable_programs"]: if "pch_exec" in program: continue temp_path = program.replace(out_dir, "").split(os.sep) + temp_path.pop(0) # remove first path separator # 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 + "-", "") @@ -555,14 +558,35 @@ def get_program_shortcuts(build_profile, ns3_version): source_shortcut = True program = program.strip() + longest_shortcut = None while len(temp_path): # Shortcuts: /src/aodv/examples/aodv can be accessed with aodv/examples/aodv, examples/aodv, aodv shortcut_path = os.sep.join(temp_path) - ns3_program_map[shortcut_path] = program + if not longest_shortcut: + longest_shortcut = shortcut_path + + # Store longest shortcut path for collisions + if shortcut_path not in longest_shortcut_map: + longest_shortcut_map[shortcut_path] = [longest_shortcut] + else: + longest_shortcut_map[shortcut_path].append(longest_shortcut) + + ns3_program_map[shortcut_path] = [program] if source_shortcut: - ns3_program_map[shortcut_path + ".cc"] = program + cc_shortcut_path = shortcut_path + ".cc" + ns3_program_map[cc_shortcut_path] = [program] + + # Store longest shortcut path for collisions + if cc_shortcut_path not in longest_shortcut_map: + longest_shortcut_map[cc_shortcut_path] = [longest_shortcut] + else: + longest_shortcut_map[cc_shortcut_path].append(longest_shortcut) temp_path.pop(0) + # Filter collisions + collisions = list(filter(lambda x: x if len(x[1]) > 1 else None, longest_shortcut_map.items())) + for (colliding_shortcut, longest_shortcuts) in collisions: + ns3_program_map[colliding_shortcut] = longest_shortcuts if programs_dict["ns3_runnable_scripts"]: scratch_scripts = glob.glob(os.path.join(ns3_path, "scratch", "*.py"), recursive=True) @@ -573,7 +597,7 @@ def get_program_shortcuts(build_profile, ns3_version): program = program.strip() while len(temp_path): shortcut_path = os.sep.join(temp_path) - ns3_program_map[shortcut_path] = program + ns3_program_map[shortcut_path] = [program] temp_path.pop(0) return ns3_program_map @@ -589,12 +613,12 @@ def cmake_check_version(): cmake = shutil.which("cmake") if not cmake: print("Error: CMake not found; please install version 3.10 or greater, or modify $PATH") - exit(-1) + 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) + exit(1) return cmake, version @@ -970,18 +994,26 @@ def main(): # We end things earlier when cleaning return - # Docs options become cmake targets - if args.docs: - args.build = [args.docs] if args.docs != "all" else ["sphinx", "doxygen"] - # Installation and uninstallation options become cmake targets if args.install: args.build = ['install'] if args.uninstall: args.build = ['uninstall'] - # Get build profile - build_profile, ns3_version, ns3_modules, enable_sudo = check_build_profile(out_dir) + # Get build profile and other settings + c4che_info, ns3_modules = check_c4che_data(out_dir) + build_profile = c4che_info["BUILD_PROFILE"] + enable_sudo = c4che_info["ENABLE_SUDO"] + ns3_version = c4che_info["VERSION"] + + # Docs options become cmake targets + if args.docs: + args.build = [args.docs] if args.docs != "all" else ["sphinx", "doxygen"] + if "doxygen" in args.build and (not c4che_info["ENABLE_EXAMPLES"] or not c4che_info["ENABLE_TESTS"]): + print('The "./ns3 docs doxygen" and "./ns3 docs all" commands,\n' + 'require examples and tests to generate introspected documentation.\n' + 'Try "./ns3 docs doxygen-no-build" or enable examples and tests.') + exit(1) if args.check_profile: if build_profile: @@ -1052,17 +1084,25 @@ def main(): # Now that CMake is configured, we can look for c++ targets in build-status.py ns3_programs = get_program_shortcuts(build_profile, ns3_version) + def check_ambiguous_target(target_type, target_to_run, ns3_programs): + if len(ns3_programs[target_to_run]) > 1: + print('%s target "%s" is ambiguous. Try one of these: "%s"' + % (target_type, target_to_run, '", "'.join(ns3_programs[target_to_run]))) + exit(1) + return ns3_programs[target_to_run][0] + # If we have a target to run, replace shortcut with full path or raise exception if run_only or build_and_run: if target_to_run in ns3_programs: - target_to_run = ns3_programs[target_to_run] + target_to_run = check_ambiguous_target("Run", target_to_run, ns3_programs) else: raise Exception("Couldn't find the specified program: %s" % target_to_run) if "build" in args: complete_targets = [] for target in args.build: - complete_targets.append(ns3_programs[target] if target in ns3_programs else target) + build_target = check_ambiguous_target("Build", target, ns3_programs) if target in ns3_programs else target + complete_targets.append(build_target) args.build = complete_targets del complete_targets @@ -1108,7 +1148,7 @@ def main(): # Setup program as sudo if enable_sudo or (args.run and args.enable_sudo): - sudo_step(args, target_to_run, set(ns3_programs.values()) if enable_sudo else set()) + sudo_step(args, target_to_run, set(ns3_programs.values()[0]) if enable_sudo else set()) # Finally, we try to run it if args.check or args.shell or run_only or build_and_run: diff --git a/src/click/CMakeLists.txt b/src/click/CMakeLists.txt index 0adc166d7..d12601dc4 100644 --- a/src/click/CMakeLists.txt +++ b/src/click/CMakeLists.txt @@ -30,9 +30,3 @@ set(test_sources test/ipv4-click-routing-test.cc) build_lib("${name}" "${source_files}" "${header_files}" "${libraries_to_link}" "${test_sources}" ) - -# click install headers when finished and we can't start compiling before that -# happens -if(NOT ${XCODE}) - add_dependencies(${libclick-obj} ${click_dep}) -endif() diff --git a/src/click/examples/CMakeLists.txt b/src/click/examples/CMakeLists.txt index 2afa61ae3..561f5cd66 100644 --- a/src/click/examples/CMakeLists.txt +++ b/src/click/examples/CMakeLists.txt @@ -1,5 +1,3 @@ -link_libraries(click_dep) - set(name nsclick-simple-lan) set(source_files ${name}.cc) set(header_files) diff --git a/utils/tests/test-ns3.py b/utils/tests/test-ns3.py index 4ac870104..eebd1c656 100644 --- a/utils/tests/test-ns3.py +++ b/utils/tests/test-ns3.py @@ -1332,6 +1332,55 @@ class NS3BuildBaseTestCase(NS3BaseTestCase): return_code, stdout, stderr = run_program("./test.py", "-p src/core/examples/sample-simulator.py", python=True) self.assertEqual(return_code, 0) + def test_11_AmbiguityCheck(self): + """! + Test if ns3 can alert correctly in case a shortcut collision happens + @return None + """ + # Copy second.cc from the tutorial examples to the scratch folder + shutil.copy("./examples/tutorial/second.cc", "./scratch/second.cc") + + # Reconfigure to re-scan the scratches + return_code, stdout, stderr = run_ns3("configure --enable-examples") + self.assertEqual(return_code, 0) + + # Try to run second and collide + return_code, stdout, stderr = run_ns3("build second") + self.assertEqual(return_code, 1) + self.assertIn( + 'Build target "second" is ambiguous. Try one of these: "scratch/second", "examples/tutorial/second"', + stdout + ) + + # Try to run scratch/second and succeed + return_code, stdout, stderr = run_ns3("build scratch/second") + self.assertEqual(return_code, 0) + self.assertIn(cmake_build_target_command(target="scratch_second"), stdout) + + # Try to run scratch/second and succeed + return_code, stdout, stderr = run_ns3("build tutorial/second") + self.assertEqual(return_code, 0) + self.assertIn(cmake_build_target_command(target="second"), stdout) + + # Try to run second and collide + return_code, stdout, stderr = run_ns3("run second") + self.assertEqual(return_code, 1) + self.assertIn( + 'Run target "second" is ambiguous. Try one of these: "scratch/second", "examples/tutorial/second"', + stdout + ) + + # Try to run scratch/second and succeed + return_code, stdout, stderr = run_ns3("run scratch/second") + self.assertEqual(return_code, 0) + + # Try to run scratch/second and succeed + return_code, stdout, stderr = run_ns3("run tutorial/second") + self.assertEqual(return_code, 0) + + # Remove second + os.remove("./scratch/second.cc") + class NS3ExpectedUseTestCase(NS3BaseTestCase): """!