From 45a4648ddae4399d208fadf56fca32d64dabcf84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Musia=C5=82?= <111433005+SpectraL519@users.noreply.github.com> Date: Thu, 16 May 2024 22:24:50 +0200 Subject: [PATCH] YT-CPPAP-7: CMake integration - Added the root `CMakeLists.txt` and `cmake/cpp-ap-config.cmake.in` files to enable CMake integration for the library - Removed the example directory and added the `cpp-ap-demo` submodule - Modified the README file and GitHub workflows to account for the changes --- .github/workflows/clang.yaml | 21 +++--- .github/workflows/gpp.yaml | 18 +++-- .github/workflows/test.yaml | 39 ----------- .gitignore | 4 +- .gitmodules | 3 + CMakeLists.txt | 77 +++++++++++++++++++++ README.md | 106 +++++++++++++++++------------ cmake/cpp-ap-config.cmake.in | 7 ++ cpp-ap-demo | 1 + example/CMakeLists.txt | 49 ------------- example/source/convert_numbers.cpp | 51 -------------- example/source/merge_files.cpp | 45 ------------ example/source/power.cpp | 46 ------------- example/source/verbosity.cpp | 70 ------------------- test/CMakeLists.txt | 32 ++++----- 15 files changed, 188 insertions(+), 381 deletions(-) delete mode 100644 .github/workflows/test.yaml create mode 100644 .gitmodules create mode 100644 CMakeLists.txt create mode 100644 cmake/cpp-ap-config.cmake.in create mode 160000 cpp-ap-demo delete mode 100644 example/CMakeLists.txt delete mode 100644 example/source/convert_numbers.cpp delete mode 100644 example/source/merge_files.cpp delete mode 100644 example/source/power.cpp delete mode 100644 example/source/verbosity.cpp diff --git a/.github/workflows/clang.yaml b/.github/workflows/clang.yaml index dce6066..990f522 100644 --- a/.github/workflows/clang.yaml +++ b/.github/workflows/clang.yaml @@ -6,27 +6,32 @@ on: jobs: build: - name: Build examples + name: Build and run tests runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 - - name: Prepare - shell: bash + - name: Prepare env run: | sudo bash ./scripts/env/install_clang17_toolchain.sh continue-on-error: false - - name: Build - shell: bash + - name: Prepare env: CC: clang-17 CXX: clang++-17 run: | - cd example cmake -B build -DCMAKE_CXX_COMPILER=clang++-17 -DCMAKE_C_COMPILER=clang-17 - cd build - make + continue-on-error: false + + - name: Build test executable + run: | + cd build && make + continue-on-error: false + + - name: Run tests + run: | + ./build/test/run_tests continue-on-error: false diff --git a/.github/workflows/gpp.yaml b/.github/workflows/gpp.yaml index d561f21..347cf92 100644 --- a/.github/workflows/gpp.yaml +++ b/.github/workflows/gpp.yaml @@ -6,21 +6,27 @@ on: jobs: build: - name: Build examples + name: Build and run tests runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 - - name: Build - shell: bash + - name: Prepare env: CC: gcc-11 CXX: g++-11 run: | - cd example cmake -B build -DCMAKE_CXX_COMPILER=g++-11 -DCMAKE_C_COMPILER=gcc-11 - cd build - make + continue-on-error: false + + - name: Build test executable + run: | + cd build && make + continue-on-error: false + + - name: Run tests + run: | + ./build/test/run_tests continue-on-error: false diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml deleted file mode 100644 index 8bd374a..0000000 --- a/.github/workflows/test.yaml +++ /dev/null @@ -1,39 +0,0 @@ -name: test -on: - push: - branches: - - '*' - -jobs: - build_and_test: - name: Build and Test - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Prepare - env: - CC: gcc-11 - CXX: g++-11 - run: | - cd test - cmake -B build -DCMAKE_CXX_COMPILER=g++-11 -DCMAKE_C_COMPILER=gcc-11 - continue-on-error: false - - - name: Build test executable - shell: bash - env: - CC: gcc-11 - CXX: g++-11 - run: | - cd test/build - make - continue-on-error: false - - - name: Run tests - run: | - cd test/build - ./test - continue-on-error: false diff --git a/.gitignore b/.gitignore index a006ac9..5a9f2b4 100644 --- a/.gitignore +++ b/.gitignore @@ -3,9 +3,7 @@ # build files *.exe -test/test -test/build/ -example/build/ +build/ # documentation files /documentation diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..c44cc23 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "cpp-ap-demo"] + path = cpp-ap-demo + url = git@github.com:SpectraL519/cpp-ap-demo.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..7f147eb --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,77 @@ +cmake_minimum_required(VERSION 3.12) + +# Check if CPP-AP is a top level project +if (NOT DEFINED PROJECT_NAME) + set(CPP_AP_IS_TOP_LEVEL_PROJECT ON) +else() + set(CPP_AP_IS_TOP_LEVEL_PROJECT OFF) +endif() + +project(cpp-ap + VERSION 1.0 + DESCRIPTION "Command-line argument parser for C++20" + HOMEPAGE_URL "https://github.com/SpectraL519/cpp-ap" + LANGUAGES CXX +) + +option(BUILD_TESTS "Build project tests" ON) + +# Define the library target +add_library(cpp-ap INTERFACE) +target_include_directories(cpp-ap INTERFACE + $ + $ +) +set_target_properties(cpp-ap PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED YES +) + +# Installation configuration +include(GNUInstallDirs) +include(CMakePackageConfigHelpers) + +# Install the headers +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + +# Install the library target +install(TARGETS cpp-ap EXPORT cpp-ap-targets) + +# Create a Config file for find_package +configure_package_config_file( + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/cpp-ap-config.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/cpp-ap-config.cmake + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cpp-ap +) + +write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/cpp-ap-config-version.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY ExactVersion +) + +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/cpp-ap-config.cmake + ${CMAKE_CURRENT_BINARY_DIR}/cpp-ap-config-version.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cpp-ap +) + +install(EXPORT cpp-ap-targets + FILE cpp-ap-targets.cmake + NAMESPACE cpp-ap:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cpp-ap +) + +# Include test directory if CPP-AP is a top level project +if (CPP_AP_IS_TOP_LEVEL_PROJECT AND BUILD_TESTS) + add_subdirectory(test) +endif() + +# Exporting from the build tree +export(EXPORT cpp-ap-targets + FILE ${CMAKE_CURRENT_BINARY_DIR}/cpp-ap-targets.cmake + NAMESPACE cpp-ap:: +) + +export(PACKAGE cpp-ap) diff --git a/README.md b/README.md index 8b94108..4afbdcc 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ Command-line argument parser for C++20 [![g++](https://github.com/SpectraL519/cpp-ap/actions/workflows/gpp.yaml/badge.svg)](https://github.com/SpectraL519/cpp-ap/actions/workflows/g++) [![clang++](https://github.com/SpectraL519/cpp-ap/actions/workflows/clang.yaml/badge.svg)](https://github.com/SpectraL519/cpp-ap/actions/workflows/clang++) -[![test](https://github.com/SpectraL519/cpp-ap/actions/workflows/test.yaml/badge.svg)](https://github.com/SpectraL519/cpp-ap/actions/workflows/test) [![format](https://github.com/SpectraL519/cpp-ap/actions/workflows/format.yaml/badge.svg)](https://github.com/SpectraL519/cpp-ap/actions/workflows/format)
@@ -29,6 +28,10 @@ The `CPP-AP` library does not require installing any additional tools or heavy l ## Table of contents * [Tutorial](#tutorial) + * [Including CPP-AP into a project](#including-cpp-ap-into-a-project) + * [CMake integration](#cmake-integration) + * [Downloading the library](#downloading-the-library) + * [Downloading the single header](#downloading-the-single-header) * [The parser class](#the-parser-class) * [Adding arguments](#adding-arguments) * [Argument parameters](#argument-parameters) @@ -48,13 +51,58 @@ The `CPP-AP` library does not require installing any additional tools or heavy l ## Tutorial -To use the `CPP-AP` library in your project, copy the [argument_parser.hpp](include/ap/argument_parser.hpp) file into your include directory, e.g. `include/ap`. No other setup is necessary - you only need to include this header in your code: +### Including CPP-AP into a project -```c++ -#include +There are 3 main ways to include the CPP-AP library into a C++ project: + +#### CMake integration: + +For CMake projects you can simply fetch the library in your `CMakeLists.txt` file: + +```cmake +cmake_minimum_required(VERSION 3.12) + +project(my_project LANGUAGES CXX) + +# Include FetchContent module +include(FetchContent) + +# Fetch CPP-AP library +FetchContent_Declare( + cpp-ap + GIT_REPOSITORY https://github.com/SpectraL519/cpp-ap.git + GIT_TAG master # here you can specify the desired tag or branch +) + +FetchContent_MakeAvailable(cpp-ap) + +# Define the executable for the project +add_executable(my_project main.cpp) + +set_target_properties(my_project PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED YES +) + +# Link against the cpp-ap library +target_link_libraries(my_project PRIVATE cpp-ap) ``` -If you wish to use the library across multiple projects without copying the header into each one, you can copy it into a common directory and add the `-I ` option when compiling your project. +#### Downloading the library + +If you do not use CMake you can dowload the desired [library release](https://github.com/SpectraL519/cpp-ap/releases), extract it in a desired directory and simply add the `/include` to the include paths of your project. + +#### Downloading the single header + +The core of the library is a [single header file](https://github.com/SpectraL519/cpp-ap/blob/master/include/ap/argument_parser.hpp) so to be able to use the library you can simply download the `argument_parser.hpp` header and paste it into the include directory of your project. + +> [!IMPORTANT] +> To actually use the library in your project simply include the single header in you `main.cpp` file: +> ```c++ +> #include +> ``` + +
### The parser class @@ -418,35 +466,7 @@ int main(int argc, char* argv[]) { ## Examples -If you wish to test the parser functionality with some real examples then follow these steps: - -Open your terminal in the project's example directory: -```shell -cd /example -``` - -The examples' source files are in the `/example/source` directory. - -> [!NOTE] -> Each source file is a sepparate example. - -Building the examples: - -```shell -cmake -B build -cd build -make -``` - -or - -```shell -mkdir build && cd build -cmake .. -make -``` - -The compiled binaries will appear in the `/example/build/bin` directory. +The library usage examples / demo projects can be found in the [cpp-ap-demo](https://github.com/SpectraL519/cpp-ap-demo) repository.

@@ -458,43 +478,39 @@ The compiled binaries will appear in the `/example/build/bin` dire First build the testing executable: ```shell -cd /test/ cmake -B build -cd build -make +cd build && make ``` or alternatively: ```shell -cd /test/ mkdir build && cd build cmake .. make ``` +This will build the test executable `run_tests` in the `/build/test` directory. + > [!TIP] > Building on Windows - use the `-G "Unix Makefiles"` option when running CMake to build a GNU Make project instead of a default Visual Studio project. Run the tests: -> [!NOTE] -> The test executable is generated in the `/test/build` directory. - * All tests: ```shell - ./test + ./run_tests ``` * A single test suite: ```shell - ./test -ts="" + ./run_tests -ts="" ``` - > [!NOTE] - > Test suites in the project have the same names as the files they're in. +> [!NOTE] +> Test suites in the project have the same names as the files they're in.
diff --git a/cmake/cpp-ap-config.cmake.in b/cmake/cpp-ap-config.cmake.in new file mode 100644 index 0000000..cf97c93 --- /dev/null +++ b/cmake/cpp-ap-config.cmake.in @@ -0,0 +1,7 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +set_and_check(CPP_AP_INCLUDE_DIR "@PACKAGE_INSTALL_PREFIX@/@CMAKE_INSTALL_INCLUDEDIR@") + +include("${CMAKE_CURRENT_LIST_DIR}/cpp-ap-targets.cmake") diff --git a/cpp-ap-demo b/cpp-ap-demo new file mode 160000 index 0000000..d5fdcb0 --- /dev/null +++ b/cpp-ap-demo @@ -0,0 +1 @@ +Subproject commit d5fdcb0a3e75794a86940d40c4547aaf02d2ec5f diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt deleted file mode 100644 index 7db437b..0000000 --- a/example/CMakeLists.txt +++ /dev/null @@ -1,49 +0,0 @@ -# Minimum CMake version required to build the project -cmake_minimum_required(VERSION 3.12) - -# Project -project(cpp-ap-examples) - -# Structure -set(SOURCE_DIR "source") -set(INCLUDE_DIRS "include" "../include") -set(BINARY_DIR "bin") -set(EXECUTABLE_DIR "bin") - -# Source files -file(GLOB_RECURSE SOURCES "${SOURCE_DIR}/*.cpp") - -# Include dirs -include_directories(${INCLUDE_DIRS}) - -# Default compiler options -set(DEFAULT_CXX_FLAGS "-Wall -Wextra -Wcast-align -Wconversion -Wunreachable-code -Wuninitialized -pedantic -g -O3") - -# Set compiler options -if(NOT DEFINED CMAKE_CXX_FLAGS) - set(CMAKE_CXX_FLAGS ${DEFAULT_CXX_FLAGS} CACHE STRING "Default C++ compile flags" FORCE) -endif() - -# Executables -foreach(source_file ${SOURCES}) - # get file name without extensions - get_filename_component(executable_name ${source_file} NAME_WE) - - add_executable(${executable_name} ${source_file}) - # Target properties - set_target_properties(${executable_name} PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${BINARY_DIR}" - CXX_STANDARD 20 - CXX_STANDARD_REQUIRED YES - CXX_EXTENSIONS NO - ) - target_compile_options(${executable_name} PRIVATE ${CMAKE_CXX_FLAGS}) -endforeach() - -# Executable path -set(EXECUTABLE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${EXECUTABLE_DIR}") - -# Symbolic links -foreach(config ${CMAKE_CONFIGURATION_TYPES}) - file(CREATE_LINK "${BINARY_DIR}/test" "${EXECUTABLE_PATH}/test_${config}" SYMBOLIC) -endforeach(config) diff --git a/example/source/convert_numbers.cpp b/example/source/convert_numbers.cpp deleted file mode 100644 index 21ee5c9..0000000 --- a/example/source/convert_numbers.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include - -#include -#include - -int main(int argc, char* argv[]) { - ap::argument_parser parser; - parser.program_name("convert numbers") - .program_description("shows the correct way of using dthe choices parameter") - .default_optional_arguments({ ap::default_argument::optional::help }); - - parser.add_optional_argument("number", "n").nargs(ap::nargs::any()).help("positive integer value"); - parser.add_optional_argument("base", "b") - .required() - .default_value("dec") - .choices({ "bin", "dec", "hex" }) - .help("output number format base"); - - try { - parser.parse_args(argc, argv); - } - catch (const ap::argument_parser_error& err) { - std::cerr << "[ERROR] : " << err.what() << std::endl << parser << std::endl; - std::exit(EXIT_FAILURE); - } - - if (parser.value("help")) { - std::cout << parser << std::endl; - std::exit(EXIT_SUCCESS); - } - - const auto numbers = parser.values("number"); - const auto base = parser.value("base"); - - if (base == "bin") { - constexpr std::size_t nbits = sizeof(std::size_t) * 8; - for (const auto n : numbers) - std::cout << std::bitset(n) << std::endl; - } - else if (base == "dec") { - for (const auto n : numbers) - std::cout << n << std::endl; - } - else { - std::cout << std::hex; - for (const auto n : numbers) - std::cout << n << std::endl; - } - - return 0; -} diff --git a/example/source/merge_files.cpp b/example/source/merge_files.cpp deleted file mode 100644 index eed1c2a..0000000 --- a/example/source/merge_files.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include - -#include -#include -#include - -int main(int argc, char* argv[]) { - ap::argument_parser parser; - parser.program_name("merge files") - .program_description("shows the correct way of using default arguments") - .default_optional_arguments( - { ap::default_argument::optional::help, - ap::default_argument::optional::multi_input, - ap::default_argument::optional::output } - ); - - try { - parser.parse_args(argc, argv); - } - catch (const ap::argument_parser_error& err) { - std::cerr << "[ERROR] : " << err.what() << std::endl << parser << std::endl; - std::exit(EXIT_FAILURE); - } - - if (parser.value("help")) { - std::cout << parser << std::endl; - std::exit(EXIT_SUCCESS); - } - - const auto input_file_name_list = parser.values("input"); - const auto output_file_name = parser.value("output"); - - std::ofstream output_file(output_file_name); - if (not output_file.is_open()) - throw std::runtime_error("Cannot open file: " + output_file_name); - - for (const auto& input_file_name : input_file_name_list) { - std::ifstream input_file(input_file_name); - std::stringstream buff; - buff << input_file.rdbuf(); - output_file << buff.str(); - } - - return 0; -} diff --git a/example/source/power.cpp b/example/source/power.cpp deleted file mode 100644 index 2cc3499..0000000 --- a/example/source/power.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include - -#include -#include - -int main(int argc, char* argv[]) { - // create the parser class instance - ap::argument_parser parser; - parser.program_name("power calculator").program_description("calculates the value of an expression: base & exponent"); - - // add arguments - parser.add_positional_argument("base").help("the exponentation base value"); - parser.add_optional_argument("exponent", "e").nargs(ap::nargs::any()).help("the exponent value"); - - parser.default_optional_arguments({ ap::default_argument::optional::help }); - - // parse command-line arguments - try { - parser.parse_args(argc, argv); - } - catch (const ap::argument_parser_error& err) { - std::cerr << "[ERROR] : " << err.what() << std::endl << parser << std::endl; - std::exit(EXIT_FAILURE); - } - - // check for the help argument presence - if (parser.has_value("help")) { - std::cout << parser << std::endl; - std::exit(EXIT_SUCCESS); - } - - // check if any values for the `exponent` argument have been parsed - if (not parser.has_value("exponent")) { - std::cout << "no exponent values given" << std::endl; - std::exit(EXIT_SUCCESS); - } - - const double base = parser.value("base"); - const std::vector exponent_values = parser.values("exponent"); - - for (const int exponent : exponent_values) { - std::cout << base << " ^ " << exponent << " = " << std::pow(base, exponent) << std::endl; - } - - return 0; -} diff --git a/example/source/verbosity.cpp b/example/source/verbosity.cpp deleted file mode 100644 index 366853e..0000000 --- a/example/source/verbosity.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#include - -#include -#include - -namespace { - -enum class verbosity_level : uint16_t { low, mid, high }; -constexpr verbosity_level max_verbosity_level = verbosity_level::high; - -std::istream& operator>>(std::istream& input, verbosity_level& verbosity) { - uint16_t value; - input >> value; - - if (value > static_cast(max_verbosity_level)) { - std::cerr << "[ERROR] : Invalid verbosity_level value - " << value << std::endl; - std::exit(EXIT_FAILURE); - } - - verbosity = static_cast(value); - - return input; -} - - -void print_msg(const verbosity_level verbosity) { - switch (verbosity) { - case verbosity_level::low: - break; - - case verbosity_level::mid: - std::cout << "msg" << std::endl; - break; - - case verbosity_level::high: - std::cout << "this is a really verbose message" << std::endl; - break; - } -} - -} // namespace - -int main(int argc, char* argv[]) { - ap::argument_parser parser; - parser.program_name("verbosity level") - .program_description("shows the correct way of using enums as a parser argument type") - .default_optional_arguments({ ap::default_argument::optional::help }); - - parser.add_optional_argument("verbosity_level", "v") - .default_value(verbosity_level::low) - .implicit_value(verbosity_level::mid) - .nargs(1); - - try { - parser.parse_args(argc, argv); - } - catch (const ap::argument_parser_error& err) { - std::cerr << "[ERROR] : " << err.what() << std::endl << parser << std::endl; - std::exit(EXIT_FAILURE); - } - - if (parser.value("help")) { - std::cout << parser << std::endl; - std::exit(EXIT_SUCCESS); - } - - print_msg(parser.value("verbosity_level")); - - return 0; -} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8c9af97..bbbfa3d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -6,9 +6,8 @@ project(cpp-ap-test) # Structure set(SOURCE_DIRS "source" "app") -set(INCLUDE_DIRS "include" "../include") -set(BINARY_DIR ".") -set(EXECUTABLE_DIR ".") +set(INCLUDE_DIRS "include") +set(EXECUTABLE_DIR "${CMAKE_CURRENT_BINARY_DIR}") # Source files file(GLOB_RECURSE SOURCES "") @@ -17,33 +16,28 @@ foreach(SOURCE_DIR ${SOURCE_DIRS}) list(APPEND SOURCES ${CURRENT_SOURCES}) endforeach() -# Include dirs -include_directories(${INCLUDE_DIRS}) - -# Default compile options -set(DEFAULT_CXX_FLAGS "-Wall -Wextra -Wcast-align -Wconversion -Wunreachable-code -Wuninitialized -pedantic -g -O3") - # Set compile options if(NOT DEFINED CMAKE_CXX_FLAGS) - set(CMAKE_CXX_FLAGS ${DEFAULT_CXX_FLAGS} CACHE STRING "Default C++ compile flags" FORCE) + set(CMAKE_CXX_FLAGS "-Wall -Wextra -Wcast-align -Wconversion -Wunreachable-code -Wuninitialized -pedantic -g -O3" + CACHE STRING "Default C++ compile flags" FORCE) endif() # Executable -add_executable(test ${SOURCES}) +add_executable(run_tests ${SOURCES}) -# Target properties -set_target_properties(test PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${BINARY_DIR}" +set_target_properties(run_tests PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${EXECUTABLE_DIR}" CXX_STANDARD 20 CXX_STANDARD_REQUIRED YES CXX_EXTENSIONS NO ) -target_compile_options(test PRIVATE ${CMAKE_CXX_FLAGS}) -# Executable path -set(EXECUTABLE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${EXECUTABLE_DIR}") +target_include_directories(run_tests PRIVATE ${INCLUDE_DIRS}) +target_compile_options(run_tests PRIVATE ${CMAKE_CXX_FLAGS}) +target_link_libraries(run_tests PRIVATE cpp-ap) -# Symbolic links +# Create symbolic links in the build directory +set(EXECUTABLE_DIR "${CMAKE_CURRENT_BINARY_DIR}") foreach(config ${CMAKE_CONFIGURATION_TYPES}) - file(CREATE_LINK "${BINARY_DIR}/test" "${EXECUTABLE_PATH}/test_${config}" SYMBOLIC) + file(CREATE_LINK "${EXECUTABLE_DIR}/run_tests" "${EXECUTABLE_DIR}/test_${config}" SYMBOLIC) endforeach(config)