diff --git a/.gitignore b/.gitignore index 24c5af167..46a4022ed 100644 --- a/.gitignore +++ b/.gitignore @@ -48,6 +48,7 @@ cmake-build-debug/ cmake-build-relwithdebinfo/ cmake-build-minsizerel/ cmake-build-release/ +vcpkg/ .vs/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f59c5a4f..5d1a08365 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,6 +55,7 @@ option(NS3_NETANIM "Build netanim" OFF) # other options option(NS3_ENABLE_BUILD_VERSION "Embed version info into libraries" OFF) option(NS3_CCACHE "Use Ccache to speed up recompilation" ON) +option(NS3_CPM "Enable the CPM C++ library manager support" OFF) option(NS3_FAST_LINKERS "Use Mold or LLD to speed up linking if available" ON) option(NS3_FETCH_OPTIONAL_COMPONENTS "Fetch Brite, Click and Openflow dependencies" OFF @@ -82,6 +83,7 @@ option(NS3_EIGEN "Build with Eigen support" ON) option(NS3_STATIC "Build a static ns-3 library and link it against executables" OFF ) +option(NS3_VCPKG "Enable the Vcpkg C++ library manager support" OFF) option(NS3_VERBOSE "Print additional build system messages" OFF) option(NS3_VISUALIZER "Build visualizer module" ON) option(NS3_WARNINGS "Enable compiler warnings" ON) diff --git a/build-support/custom-modules/ns3-module-macros.cmake b/build-support/custom-modules/ns3-module-macros.cmake index 145746c60..82f5d0590 100644 --- a/build-support/custom-modules/ns3-module-macros.cmake +++ b/build-support/custom-modules/ns3-module-macros.cmake @@ -191,9 +191,29 @@ function(build_lib) # include directories, allowing consumers of this module to include and link # the 3rd-party code with no additional setup get_target_includes(${lib${BLIB_LIBNAME}} exported_include_directories) + string(REPLACE "-I" "" exported_include_directories "${exported_include_directories}" ) + + # include directories prefixed in the source or binary directory need to be + # treated differently + set(new_exported_include_directories) + foreach(directory ${exported_include_directories}) + string(FIND "${directory}" "${PROJECT_SOURCE_DIR}" is_prefixed_in_subdir) + if(${is_prefixed_in_subdir} GREATER_EQUAL 0) + string(SUBSTRING "${directory}" ${is_prefixed_in_subdir} -1 + directory_path + ) + list(APPEND new_exported_include_directories + $ + ) + else() + list(APPEND new_exported_include_directories ${directory}) + endif() + endforeach() + set(exported_include_directories ${new_exported_include_directories}) + string(REPLACE "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/include" "" exported_include_directories "${exported_include_directories}" diff --git a/build-support/custom-modules/ns3-vcpkg-hunter.cmake b/build-support/custom-modules/ns3-vcpkg-hunter.cmake new file mode 100644 index 000000000..db8acdc75 --- /dev/null +++ b/build-support/custom-modules/ns3-vcpkg-hunter.cmake @@ -0,0 +1,198 @@ +# Copyright (c) 2017-2023 Universidade de Brasília +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License version 2 as published by the Free +# Software Foundation; +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., 59 Temple +# Place, Suite 330, Boston, MA 02111-1307 USA +# +# Author: Gabriel Ferreira + +# Set default installation directory +set(VCPKG_DIR "${PROJECT_SOURCE_DIR}/vcpkg") + +# Check if there is an existing vcpkg installation in the user directory +if(WIN32) + set(user_dir $ENV{USERPROFILE}) +else() + set(user_dir $ENV{HOME}) +endif() + +# Override the default vcpkg installation directory in case it does +if(EXISTS "${user_dir}/vcpkg") + set(VCPKG_DIR "${user_dir}/vcpkg") +endif() + +find_package(Git) + +if(${CMAKE_SIZEOF_VOID_P} EQUAL 8) + set(VCPKG_TARGET_ARCH x64) +else() + set(VCPKG_TARGET_ARCH x86) +endif() + +if(WIN32) + set(VCPKG_EXEC vcpkg.exe) + set(VCPKG_TRIPLET ${VCPKG_TARGET_ARCH}-windows) +else() + set(VCPKG_EXEC vcpkg) + if(NOT APPLE) # LINUX + set(VCPKG_TRIPLET ${VCPKG_TARGET_ARCH}-linux) + else() + set(VCPKG_TRIPLET ${VCPKG_TARGET_ARCH}-osx) + endif() +endif() + +# https://learn.microsoft.com/en-us/vcpkg/users/buildsystems/cmake-integration +set(VCPKG_TARGET_TRIPLET ${VCPKG_TRIPLET}) +set(VCPKG_MANIFEST ${PROJECT_SOURCE_DIR}/vcpkg.json) + +function(setup_vcpkg) + message(STATUS "vcpkg: setting up support in ${VCPKG_DIR}") + + # Check if vcpkg was downloaded previously + if(EXISTS "${VCPKG_DIR}") + # Vcpkg already downloaded + message(STATUS "vcpkg: folder already exists, skipping git download") + else() + if(NOT ${Git_FOUND}) + message(FATAL_ERROR "vcpkg: Git is required, but it was not found") + endif() + get_filename_component(VCPKG_PARENT_DIR ${VCPKG_DIR} DIRECTORY) + execute_process( + COMMAND ${GIT_EXECUTABLE} clone --depth 1 + https://github.com/microsoft/vcpkg.git + WORKING_DIRECTORY "${VCPKG_PARENT_DIR}/" + ) + endif() + + if(${MSVC}) + message(FATAL_ERROR "vcpkg: Visual Studio is unsupported") + else() + # Check if required packages are installed (unzip curl tar) + if(WIN32) + find_program(ZIP_PRESENT zip.exe) + find_program(UNZIP_PRESENT unzip.exe) + find_program(CURL_PRESENT curl.exe) + find_program(TAR_PRESENT tar.exe) + else() + find_program(ZIP_PRESENT zip) + find_program(UNZIP_PRESENT unzip) + find_program(CURL_PRESENT curl) + find_program(TAR_PRESENT tar) + endif() + + if(${ZIP_PRESENT} STREQUAL ZIP_PRESENT-NOTFOUND) + message(FATAL_ERROR "vcpkg: Zip is required, but is not installed") + endif() + + if(${UNZIP_PRESENT} STREQUAL UNZIP_PRESENT-NOTFOUND) + message(FATAL_ERROR "vcpkg: Unzip is required, but is not installed") + endif() + + if(${CURL_PRESENT} STREQUAL CURL_PRESENT-NOTFOUND) + message(FATAL_ERROR "vcpkg: Curl is required, but is not installed") + endif() + + if(${TAR_PRESENT} STREQUAL TAR_PRESENT-NOTFOUND) + message(FATAL_ERROR "vcpkg: Tar is required, but is not installed") + endif() + endif() + + # message(WARNING "Checking VCPKG bootstrapping") Check if vcpkg was + # bootstrapped previously + if(EXISTS "${VCPKG_DIR}/${VCPKG_EXEC}") + message(STATUS "vcpkg: already bootstrapped") + else() + # message(WARNING "vcpkg: bootstrapping") + set(COMPILER_ENFORCING) + + if(WIN32) + set(command bootstrap-vcpkg.bat -disableMetrics) + else() + # if(NOT APPLE) #linux/bsd + set(command bootstrap-vcpkg.sh -disableMetrics) + # else() set(command bootstrap-vcpkg.sh)# --allowAppleClang) endif() + endif() + + execute_process( + COMMAND ${COMPILER_ENFORCING} ${VCPKG_DIR}/${command} + WORKING_DIRECTORY ${VCPKG_DIR} + ) + # message(STATUS "vcpkg: bootstrapped") include_directories(${VCPKG_DIR}) + set(ENV{VCPKG_ROOT} ${VCPKG_DIR}) + endif() + + if(NOT WIN32) + execute_process(COMMAND chmod +x ${VCPKG_DIR}/${VCPKG_EXEC}) + endif() + + set(CMAKE_PREFIX_PATH + "${VCPKG_DIR}/installed/${VCPKG_TRIPLET}/;${CMAKE_PREFIX_PATH}" + PARENT_SCOPE + ) + + # Install packages in manifest mode + if(EXISTS ${VCPKG_MANIFEST}) + message(STATUS "vcpkg: detected a vcpkg manifest file: ${VCPKG_MANIFEST}") + execute_process( + COMMAND ${VCPKG_DIR}/${VCPKG_EXEC} install --triplet ${VCPKG_TRIPLET} + --x-install-root=${VCPKG_DIR}/installed RESULT_VARIABLE res + ) + if(${res} EQUAL 0) + message(STATUS "vcpkg: packages defined in the manifest were installed") + else() + message( + FATAL_ERROR + "vcpkg: packages defined in the manifest failed to be installed" + ) + endif() + endif() +endfunction() + +function(add_package package_name) + # Early exit in case vcpkg support is not enabled + if(NOT ${NS3_VCPKG}) + message(${HIGHLIGHTED_STATUS} + "vcpkg: support is disabled. Not installing: ${package_name}" + ) + return() + endif() + + # Early exit in case a vcpkg.json manifest file is present + if(EXISTS ${VCPKG_MANIFEST}) + file(READ ${VCPKG_MANIFEST} contents) + if(${contents} MATCHES ${package_name}) + message(STATUS "vcpkg: ${package_name} was installed") + return() + else() + message( + FATAL_ERROR + "vcpkg: manifest mode in use, but ${package_name} is not listed in ${VCPKG_MANIFEST}" + ) + endif() + endif() + + # Normal exit + message(STATUS "vcpkg: ${package_name} will be installed") + execute_process( + COMMAND ${VCPKG_DIR}/${VCPKG_EXEC} install ${package_name} --triplet + ${VCPKG_TRIPLET} RESULT_VARIABLE res + ) + if(${res} EQUAL 0) + message(STATUS "vcpkg: ${package_name} was installed") + else() + message(FATAL_ERROR "vcpkg: ${package_name} failed to be installed") + endif() +endfunction() + +if(${NS3_VCPKG}) + setup_vcpkg() +endif() diff --git a/build-support/macros-and-definitions.cmake b/build-support/macros-and-definitions.cmake index 4c54e7e9d..73208d795 100644 --- a/build-support/macros-and-definitions.cmake +++ b/build-support/macros-and-definitions.cmake @@ -150,6 +150,7 @@ set(CMAKE_HEADER_OUTPUT_DIRECTORY ${CMAKE_OUTPUT_DIRECTORY}/include/ns3) set(THIRD_PARTY_DIRECTORY ${PROJECT_SOURCE_DIR}/3rd-party) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) link_directories(${CMAKE_OUTPUT_DIRECTORY}/lib) +file(MAKE_DIRECTORY ${CMAKE_OUTPUT_DIRECTORY}) # Get installation folder default values for each platform and include package # configuration macro @@ -751,6 +752,51 @@ macro(process_options) ) list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/build-support/3rd-party") + # Include our package managers + # cmake-format: off + # Starting with a custom cmake file that provides a Hunter-like interface to vcpkg + # Use add_package(package) to install a package + # Then find_package(package) to use it + # cmake-format: on + include(ns3-vcpkg-hunter) + + # Then the beautiful CPM manager (too bad it doesn't work with everything) + # https://github.com/cpm-cmake/CPM.cmake + if(${NS3_CPM}) + set(CPM_DOWNLOAD_VERSION 0.38.2) + set(CPM_DOWNLOAD_LOCATION + "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake" + ) + if(NOT (EXISTS ${CPM_DOWNLOAD_LOCATION})) + message(STATUS "Downloading CPM.cmake to ${CPM_DOWNLOAD_LOCATION}") + file( + DOWNLOAD + https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake + ${CPM_DOWNLOAD_LOCATION} + ) + endif() + include(${CPM_DOWNLOAD_LOCATION}) + set(CPM_USE_LOCAL_PACKAGES ON) + endif() + # Package manager test block + if(TEST_PACKAGE_MANAGER) + if(${TEST_PACKAGE_MANAGER} STREQUAL "CPM") + cpmaddpackage( + NAME ARMADILLO GIT_TAG 6cada351248c9a967b137b9fcb3d160dad7c709b + GIT_REPOSITORY https://gitlab.com/conradsnicta/armadillo-code.git + ) + find_package(ARMADILLO REQUIRED) + message(STATUS "Armadillo was found? ${ARMADILLO_FOUND}") + elseif(${TEST_PACKAGE_MANAGER} STREQUAL "VCPKG") + add_package(Armadillo) + find_package(Armadillo REQUIRED) + message(STATUS "Armadillo was found? ${ARMADILLO_FOUND}") + else() + find_package(Armadillo REQUIRED) + endif() + endif() + # End of package managers + set(ENABLE_EIGEN False) if(${NS3_EIGEN}) find_package(Eigen3 QUIET) diff --git a/build-support/test-files/test-package-managers/CMakeLists.txt b/build-support/test-files/test-package-managers/CMakeLists.txt new file mode 100644 index 000000000..32f8ae84b --- /dev/null +++ b/build-support/test-files/test-package-managers/CMakeLists.txt @@ -0,0 +1,42 @@ +if(NOT + TEST_PACKAGE_MANAGER +) + message( + STATUS "Skipping test module because TEST_PACKAGE_MANAGER is not set." + ) + return() +endif() + +set(armadillo) +if(${TEST_PACKAGE_MANAGER} + STREQUAL + "CPM" +) + set(armadillo + armadillo + ) + include_directories(${CMAKE_BINARY_DIR}/_deps/armadillo-src/include) +elseif( + ${TEST_PACKAGE_MANAGER} + STREQUAL + "VCPKG" +) + set(armadillo + ${ARMADILLO_LIBRARIES} + ) + include_directories(${ARMADILLO_INCLUDE_DIRS}) +else() + message(FATAL_ERROR "zoincs") + message( + STATUS + "Skipping test module because TEST_PACKAGE_MANAGER is set to an unsupported value." + ) + return() +endif() + +build_lib( + LIBNAME test-package-managers + SOURCE_FILES src.cc + LIBRARIES_TO_LINK ${libcore} + ${armadillo} +) diff --git a/build-support/test-files/test-package-managers/src.cc b/build-support/test-files/test-package-managers/src.cc new file mode 100644 index 000000000..515252187 --- /dev/null +++ b/build-support/test-files/test-package-managers/src.cc @@ -0,0 +1,10 @@ +#include + +int +main(int argc, char** argv) +{ + arma::arma_rng::set_seed_random(); + arma::Mat mat = arma::randu(4, 4); + std::cout << "mat:\n" << mat << "\n"; + return 0; +} diff --git a/ns3 b/ns3 index 176c6045a..a11aaed53 100755 --- a/ns3 +++ b/ns3 @@ -505,6 +505,10 @@ def clean_pip_packaging_artifacts(dry_run=False): remove_dir(directory, dry_run) +def clean_vcpkg_artifacts(dry_run=False): + remove_dir(os.path.join(ns3_path, "vcpkg"), dry_run) + + def search_cmake_cache(build_profile): # Search for the CMake cache cmake_cache_files = glob.glob("%s/**/CMakeCache.txt" % ns3_path, recursive=True) @@ -1445,6 +1449,7 @@ def main(): clean_cmake_artifacts(dry_run=args.dry_run) clean_docs_and_tests_artifacts(dry_run=args.dry_run) clean_pip_packaging_artifacts(dry_run=args.dry_run) + clean_vcpkg_artifacts(dry_run=args.dry_run) # We end things earlier when cleaning return diff --git a/utils/tests/test-ns3.py b/utils/tests/test-ns3.py index 59bf86667..34c55528d 100755 --- a/utils/tests/test-ns3.py +++ b/utils/tests/test-ns3.py @@ -1,6 +1,6 @@ #! /usr/bin/env python3 # -# Copyright (c) 2021 Universidade de Brasília +# Copyright (c) 2021-2023 Universidade de Brasília # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as @@ -2834,6 +2834,98 @@ class NS3ExpectedUseTestCase(NS3BaseTestCase): self.assertIn("scratch-simulator", stdout) self.assertIn("(lldb) target create", stdout) + def test_18_CpmAndVcpkgManagers(self): + """! + Test if CPM and Vcpkg package managers are working properly + @return None + """ + # Clean the ns-3 configuration + return_code, stdout, stderr = run_ns3("clean") + self.assertEqual(return_code, 0) + + # Cleanup VcPkg leftovers + if os.path.exists("vcpkg"): + shutil.rmtree("vcpkg") + + # Copy a test module that consumes armadillo + destination_src = os.path.join(ns3_path, "src/test-package-managers") + # Remove pre-existing directories + if os.path.exists(destination_src): + shutil.rmtree(destination_src) + + # Always use a fresh copy + shutil.copytree(os.path.join(ns3_path, "build-support/test-files/test-package-managers"), + destination_src) + + with DockerContainerManager(self, "ubuntu:22.04") as container: + # Install toolchain + container.execute("apt-get update") + container.execute("apt-get install -y python3 cmake g++ ninja-build") + + # Verify that Armadillo is not available and that we did not + # add any new unnecessary dependency when features are not used + try: + container.execute( + "./ns3 configure -- -DTEST_PACKAGE_MANAGER:STRING=ON") + self.skipTest("Armadillo is already installed") + except DockerException as e: + pass + + # Clean cache to prevent dumb errors + return_code, stdout, stderr = run_ns3("clean") + self.assertEqual(return_code, 0) + + # Install CPM and VcPkg shared dependency + container.execute("apt-get install -y git") + + # Install Armadillo with CPM + try: + container.execute( + "./ns3 configure -- -DNS3_CPM=ON -DTEST_PACKAGE_MANAGER:STRING=CPM") + except DockerException as e: + self.fail() + + # Try to build module using CPM's Armadillo + try: + container.execute( + "./ns3 build test-package-managers") + except DockerException as e: + self.fail() + + # Clean cache to prevent dumb errors + return_code, stdout, stderr = run_ns3("clean") + self.assertEqual(return_code, 0) + + # Install VcPkg dependencies + container.execute("apt-get install -y zip unzip tar curl") + + # Install Armadillo dependencies + container.execute("apt-get install -y pkg-config gfortran") + + # Install VcPkg + try: + container.execute( + "./ns3 configure -- -DNS3_VCPKG=ON") + except DockerException as e: + self.fail() + + # Install Armadillo with VcPkg + try: + container.execute("./ns3 configure -- -DTEST_PACKAGE_MANAGER:STRING=VCPKG") + except DockerException as e: + self.fail() + + # Try to build module using VcPkg's Armadillo + try: + container.execute( + "./ns3 build test-package-managers") + except DockerException as e: + self.fail() + + # Remove test module + if os.path.exists(destination_src): + shutil.rmtree(destination_src) + class NS3QualityControlTestCase(unittest.TestCase): """!