diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml new file mode 100644 index 0000000..61f380c --- /dev/null +++ b/.github/workflows/windows.yml @@ -0,0 +1,64 @@ +--- +name: windows + +on: + pull_request: + push: + branches: + - master + - develop + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + runs-on: ${{matrix.os}} + strategy: + fail-fast: false + matrix: + os: [windows-latest] + # TODO: platform: [Win32, x64] + platform: [x64] + cxx_standard: [20, 23] + # NO! shared: ["", -DBUILD_SHARED_LIBS=ON] + build_type: [Release] + + steps: + - uses: actions/checkout@v3 + + - name: Create Build Environment + run: | + pip install cmake ninja + ninja --version + cmake --version + cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure + # Use a bash shell for $GITHUB_WORKSPACE. + shell: bash + working-directory: ${{runner.workspace}}/build + run: | + cmake -G Ninja -DCMAKE_SYSTEM_PROCESSOR=${{matrix.platform}} \ + -DCMAKE_TOOLCHAIN_FILE=cmake/Windows.MSVC.toolchain.cmake \ + -DCMAKE_CXX_STANDARD=${{matrix.cxx_standard}} \ + ${{matrix.shared}} -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \ + $GITHUB_WORKSPACE + + - name: Build + working-directory: ${{runner.workspace}}/build + run: | + $threads = (Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors + cmake --build . --config ${{matrix.build_type}} --parallel $threads + + - name: Test + working-directory: ${{runner.workspace}}/build + run: ctest -C ${{matrix.build_type}} --timeout 60 + env: + CTEST_OUTPUT_ON_FAILURE: True + + - name: Install + working-directory: ${{runner.workspace}}/build + run: | + cmake --install . --config ${{matrix.build_type}} diff --git a/CMakeLists.txt b/CMakeLists.txt index 55bc59a..06009ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,42 +1,73 @@ -cmake_minimum_required(VERSION 3.15) -cmake_policy(VERSION 3.15) +cmake_minimum_required(VERSION 3.28) + +#--------------------------------------------------------------------------------------- +# Compiler config +#--------------------------------------------------------------------------------------- +if(NOT DEFINED CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 20) + set(CMAKE_CXX_STANDARD_REQUIRED ON) + set(CMAKE_CXX_EXTENSIONS OFF) +endif() + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) project(Modules - VERSION 0.0.0 + VERSION 0.0.1 LANGUAGES CXX) set(CMAKE_CONFIGURATION_TYPES "Release;Debug" CACHE STRING "" FORCE) set_property(GLOBAL PROPERTY USE_FOLDERS ON) set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "_cmake") -include(GnuInstallDirs) +include(GNUInstallDirs) + +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") + message(FATAL_ERROR "CMAKE_CXX_SCAN_FOR_MODULES not supported yet") +else() + set(CMAKE_CXX_SCAN_FOR_MODULES 1) +endif() -add_compile_options(/experimental:module) # needed for STL modules +if(WIN32) + add_compile_options(/experimental:module) # needed for STL modules +endif() + +if(PROJECT_IS_TOP_LEVEL) + option(TEST_INSTALLED_VERSION "Test exported cmake config set" NO) + enable_testing() +endif() + +if(NOT TEST_INSTALLED_VERSION) # # Single file module # add_executable(SingleFileModule) target_sources(SingleFileModule - PRIVATE + PRIVATE SingleFileModule/main.cpp + PRIVATE + FILE_SET cxx_modules TYPE CXX_MODULES FILES SingleFileModule/mod.ixx ) set_property(TARGET SingleFileModule PROPERTY CXX_STANDARD 20) +add_test(NAME SingleFileModule COMMAND SingleFileModule) # # Two files module # add_executable(TwoFileModule) target_sources(TwoFileModule - PRIVATE + PRIVATE TwoFileModule/main.cpp - TwoFileModule/mod.ixx TwoFileModule/mod.cpp + PRIVATE + FILE_SET cxx_modules TYPE CXX_MODULES FILES + TwoFileModule/mod.ixx ) set_property(TARGET TwoFileModule PROPERTY CXX_STANDARD 20) +add_test(NAME TwoFileModule COMMAND TwoFileModule) # # Partition module @@ -44,14 +75,19 @@ set_property(TARGET TwoFileModule PROPERTY CXX_STANDARD 20) add_executable(PartitionModule) target_sources(PartitionModule - PRIVATE + PRIVATE PartitionModule/main.cpp + PartitionModule/mod.func.cpp + PartitionModule/mod.class.cpp + PRIVATE + FILE_SET cxx_modules TYPE CXX_MODULES FILES PartitionModule/mod.ixx - PartitionModule/mod.class.ixx PartitionModule/mod.class.cpp - PartitionModule/mod.func.ixx PartitionModule/mod.func.cpp + PartitionModule/mod.class.ixx + PartitionModule/mod.func.ixx ) set_property(TARGET PartitionModule PROPERTY CXX_STANDARD 20) +add_test(NAME PartitionModule COMMAND PartitionModule) # # Conflicts - Two modules including same headers (iostream) @@ -59,13 +95,16 @@ set_property(TARGET PartitionModule PROPERTY CXX_STANDARD 20) add_executable(Conflicts) target_sources(Conflicts - PRIVATE + PRIVATE Conflicts/main.cpp + PRIVATE + FILE_SET cxx_modules TYPE CXX_MODULES FILES Conflicts/mod.ixx Conflicts/mod2.ixx ) set_property(TARGET Conflicts PROPERTY CXX_STANDARD 20) +add_test(NAME Conflicts COMMAND Conflicts) # # SubModules @@ -73,18 +112,22 @@ set_property(TARGET Conflicts PROPERTY CXX_STANDARD 20) add_executable(SubModules) target_sources(SubModules - PRIVATE + PRIVATE SubModules/main.cpp - + SubModules/mod/sub1/mod.sub1.class.cpp # Partitions od mod.sub1 module/submodule + SubModules/mod/sub1/mod.sub1.func.cpp # + PRIVATE + FILE_SET cxx_modules TYPE CXX_MODULES FILES SubModules/mod/mod.ixx # PMI for module mod SubModules/mod/sub1/mod.sub1.ixx # PMI for submodule mod.sub1 - SubModules/mod/sub1/mod.sub1.class.ixx SubModules/mod/sub1/mod.sub1.class.cpp # Partitions od mod.sub1 module/submodule - SubModules/mod/sub1/mod.sub1.func.ixx SubModules/mod/sub1/mod.sub1.func.cpp # + SubModules/mod/sub1/mod.sub1.class.ixx + SubModules/mod/sub1/mod.sub1.func.ixx SubModules/mod/sub2/mod.sub2.ixx # PMI for submodule mod.sub2 ) set_property(TARGET SubModules PROPERTY CXX_STANDARD 20) #set_property(TARGET SubModules PROPERTY FOLDER "Submodule") +add_test(NAME SubModules COMMAND SubModules) # # Module as dll @@ -92,13 +135,14 @@ set_property(TARGET SubModules PROPERTY CXX_STANDARD 20) add_library(DllModule SHARED) target_sources(DllModule - PRIVATE + PRIVATE DllModule/mod.class.cpp DllModule/mod.func.cpp PUBLIC + FILE_SET cxx_modules TYPE CXX_MODULES FILES DllModule/mod.ixx DllModule/mod.class.ixx - DllModule/mod.func.ixx + DllModule/mod.func.ixx ) set_property(TARGET DllModule PROPERTY CXX_STANDARD 20) @@ -112,7 +156,7 @@ set_property(TARGET DllModule PROPERTY FOLDER "Dll") add_executable(DllModuleApp) target_sources(DllModuleApp - PRIVATE + PRIVATE DllModule/main.cpp ) @@ -129,17 +173,15 @@ add_library(ExternalDllModule SHARED) set(ixx_files ExternalDllModule/mod.ixx ExternalDllModule/mod.class.ixx - ExternalDllModule/mod.func.ixx + ExternalDllModule/mod.func.ixx ) target_sources(ExternalDllModule - PRIVATE + PRIVATE ExternalDllModule/mod.class.cpp ExternalDllModule/mod.func.cpp - $ - INTERFACE - $ - $ - $ + PUBLIC + FILE_SET cxx_modules_dll TYPE CXX_MODULES FILES + ${ixx_files} ) set_property(TARGET ExternalDllModule PROPERTY CXX_STANDARD 20) target_compile_definitions(ExternalDllModule @@ -148,27 +190,39 @@ target_compile_definitions(ExternalDllModule INTERFACE "DLLMODULE_EXPORT=__declspec(dllimport)" ) -install(TARGETS ExternalDllModule EXPORT ExternalDllModule_export) -install(FILES ${ixx_files} TYPE INCLUDE) # there is no standard destination now so we pick include -install(EXPORT ExternalDllModule_export NAMESPACE ExternalDllModule:: DESTINATION lib/cmake/ExternalDllModule FILE ExternalDllModuleConfig.cmake) set_property(TARGET ExternalDllModule PROPERTY FOLDER "Dll") +# +# install the ExternalDllModule +# + +install(TARGETS ExternalDllModule EXPORT ExternalDllModule_export + FILE_SET cxx_modules_dll DESTINATION lib/cxx/miu +) +#XXX install(FILES ${ixx_files} TYPE INCLUDE) # there is no standard destination now so we pick include +install(EXPORT ExternalDllModule_export NAMESPACE ExternalDllModule:: + DESTINATION lib/cmake/ExternalDllModule FILE ExternalDllModuleConfig.cmake +) + +endif() # NOT TEST_INSTALLED_VERSION + # # 3rd party dll module consumption -# You need to install the ExternalDllModule first # -find_package(ExternalDllModule QUIET) -if(ExternalDllModule_FOUND) +if(NOT TARGET ExternalDllModule) + find_package(ExternalDllModule REQUIRED) + add_executable(ExternalDllModuleApp) target_sources(ExternalDllModuleApp - PRIVATE + PRIVATE DllModule/main.cpp ) target_link_libraries(ExternalDllModuleApp PRIVATE ExternalDllModule::ExternalDllModule) set_property(TARGET ExternalDllModuleApp PROPERTY CXX_STANDARD 20) set_property(TARGET ExternalDllModuleApp PROPERTY FOLDER "Dll") + add_test(NAME ExternalDllModuleApp COMMAND ExternalDllModuleApp) endif() -SET_PROPERTY(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT SingleFileModule) \ No newline at end of file +SET_PROPERTY(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT SingleFileModule) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..062e9d6 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,58 @@ +{ + "version": 7, + "cmakeMinimumRequired": { + "major": 3, + "minor": 27, + "patch": 0 + }, + "include": [ + "CMakeReleasePresets.json" + ], + "configurePresets": [ + { + "name": "ninja-multi", + "inherits": "Release", + "displayName": "Ninja Multi-Config", + "description": "Build using Ninja Multi-Config generator", + "generator": "Ninja Multi-Config" + }, + { + "name": "windows-only", + "inherits": "Release", + "displayName": "Windows-only configuration", + "description": "This build is only available on Windows", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + } + ], + "workflowPresets": [ + { + "name": "Release", + "steps": [ + { + "type": "configure", + "name": "Release" + }, + { + "type": "build", + "name": "Release" + }, + { + "type": "test", + "name": "Release" + }, + { + "type": "build", + "name": "install" + }, + { + "type": "package", + "name": "Release" + } + ] + } + ] +} diff --git a/CMakeReleasePresets.json b/CMakeReleasePresets.json new file mode 100644 index 0000000..a7c2cbc --- /dev/null +++ b/CMakeReleasePresets.json @@ -0,0 +1,86 @@ +{ + "version": 7, + "cmakeMinimumRequired": { + "major": 3, + "minor": 27, + "patch": 0 + }, + "configurePresets": [ + { + "name": "Release", + "displayName": "Release Config", + "description": "Release build using Ninja generator", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/Release", + "installDir": "${sourceDir}/stagedir", + "cacheVariables": { + "CMAKE_PREFIX_PATH": { + "type": "path", + "value": "${sourceDir}/stagedir" + }, + "BUILD_SHARED_LIBS": true, + "CMAKE_CXX_STANDARD": "20", + "CMAKE_CXX_STANDARD_REQUIRED": true, + "CMAKE_CXX_EXTENSIONS": false, + "CMAKE_EXPORT_COMPILE_COMMANDS": true, + "CMAKE_SKIP_INSTALL_RULES": false, + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_DEBUG_POSTFIX": "D", + "FORMAT_SCIP_CLANG": true + }, + "environment": { + "CPM_USE_LOCAL_PACKAGES": "NO", + "PATH": "$env{HOME}/.local/bin${pathListSep}$penv{PATH}" + }, + "warnings": { + "deprecated": true, + "uninitialized": false + }, + "trace": { + "mode": "off" + } + } + ], + "buildPresets": [ + { + "name": "Release", + "configurePreset": "Release" + }, + { + "name": "install", + "configurePreset": "Release", + "targets": [ + "install" + ] + }, + { + "name": "format", + "configurePreset": "Release", + "targets": [ + "fix-cmake-format" + ] + } + ], + "testPresets": [ + { + "name": "Release", + "configurePreset": "Release", + "output": { + "outputOnFailure": true + }, + "execution": { + "noTestsAction": "error", + "stopOnFailure": true + } + } + ], + "packagePresets": [ + { + "name": "Release", + "configurePreset": "Release", + "generators": [ + "TGZ" + ] + } + ] +} diff --git a/cmake/.cmake-format.yaml b/cmake/.cmake-format.yaml new file mode 100644 index 0000000..7bbf2f7 --- /dev/null +++ b/cmake/.cmake-format.yaml @@ -0,0 +1,45 @@ +format: + command_case: lower + dangle_parens: true + line_ending: unix + line_width: 120 + max_pargs_hwrap: 3 + separate_ctrl_name_with_space: false + separate_fn_name_with_space: false + tab_size: 4 + use_tabchars: false + +parse: + additional_commands: + file: + pargs: + flags: [ + 'ARCHIVE_EXTRACT' + ] + kwargs: + INPUT: 1 + DESTINATION: 1 + +markup: + bullet_char: "*" + enable_markup: false + enum_char: . + +lint: + argument_var_pattern: '([A-Z][A-Z0-9_]+|[a-z_][a-z0-9_]+)' + local_var_pattern: '[A-Z][A-Z0-9_]+' + max_statements: 60 + disabled_codes: + # Disable "Line too long", the current documentation tables have long lines. + - C0301 + + # Disable "Wrong line ending (unix)". See https://github.com/cheshirekow/cmake_format/issues/273 + - C0327 + + # Disable "Empty docstring on function or macro declaration". CMakeLang doesn't appear to handle bracket comments + - C0112 + +encode: + emit_byteorder_mark: false + input_encoding: utf-8 + output_encoding: utf-8 diff --git a/cmake/AddUninstallTarget.cmake b/cmake/AddUninstallTarget.cmake new file mode 100644 index 0000000..15997f7 --- /dev/null +++ b/cmake/AddUninstallTarget.cmake @@ -0,0 +1,107 @@ +# SPDX-FileCopyrightText: 2012-2021 Istituto Italiano di Tecnologia (IIT) +# SPDX-FileCopyrightText: 2008-2013 Kitware Inc. +# SPDX-License-Identifier: BSD-3-Clause + +#[=======================================================================[.rst: +AddUninstallTarget +------------------ + +Add the "uninstall" target for your project:: + + include(AddUninstallTarget) + + +will create a file ``cmake_uninstall.cmake`` in the build directory and add a +custom target ``uninstall`` (or ``UNINSTALL`` on Visual Studio and Xcode) that +will remove the files installed by your package (using +``install_manifest.txt``). +See also +https://gitlab.kitware.com/cmake/community/wikis/FAQ#can-i-do-make-uninstall-with-cmake + +The :module:`AddUninstallTarget` module must be included in your main +``CMakeLists.txt``. If included in a subdirectory it does nothing. +This allows you to use it safely in your main ``CMakeLists.txt`` and include +your project using ``add_subdirectory`` (for example when using it with +:cmake:module:`FetchContent`). + +If the ``uninstall`` target already exists, the module does nothing. +#]=======================================================================] + +# AddUninstallTarget works only when included in the main CMakeLists.txt +if(NOT + "${CMAKE_CURRENT_BINARY_DIR}" + STREQUAL + "${CMAKE_BINARY_DIR}" +) + return() +endif() + +# The name of the target is uppercase in MSVC and Xcode (for coherence with the +# other standard targets) +if("${CMAKE_GENERATOR}" MATCHES "^(Visual Studio|Xcode)") + set(_uninstall "UNINSTALL") +else() + set(_uninstall "uninstall") +endif() + +# If target is already defined don't do anything +if(TARGET ${_uninstall}) + return() +endif() + +set(_filename cmake_uninstall.cmake) + +file( + WRITE + "${CMAKE_CURRENT_BINARY_DIR}/${_filename}" + "if(NOT EXISTS \"${CMAKE_CURRENT_BINARY_DIR}/install_manifest.txt\") + message(WARNING \"Cannot find install manifest: \\\"${CMAKE_CURRENT_BINARY_DIR}/install_manifest.txt\\\"\") + return() +endif() + +file(READ \"${CMAKE_CURRENT_BINARY_DIR}/install_manifest.txt\" files) +string(STRIP \"\${files}\" files) +string(REGEX REPLACE \"\\n\" \";\" files \"\${files}\") +list(REVERSE files) +foreach(file \${files}) + if(IS_SYMLINK \"\$ENV{DESTDIR}\${file}\" OR EXISTS \"\$ENV{DESTDIR}\${file}\") + message(STATUS \"Uninstalling: \$ENV{DESTDIR}\${file}\") + execute_process( + COMMAND \${CMAKE_COMMAND} -E remove \"\$ENV{DESTDIR}\${file}\" + OUTPUT_VARIABLE rm_out + RESULT_VARIABLE rm_retval) + if(NOT \"\${rm_retval}\" EQUAL 0) + message(FATAL_ERROR \"Problem when removing \\\"\$ENV{DESTDIR}\${file}\\\"\") + endif() + else() + message(STATUS \"Not-found: \$ENV{DESTDIR}\${file}\") + endif() +endforeach(file) +" +) + +set(_desc "Uninstall the project...") +if(CMAKE_GENERATOR STREQUAL "Unix Makefiles") + set(_comment + COMMAND + \$\(CMAKE_COMMAND\) + -E + cmake_echo_color + --switch=$\(COLOR\) + --cyan + "${_desc}" + ) +else() + set(_comment COMMENT "${_desc}") +endif() +add_custom_target( + ${_uninstall} + ${_comment} + COMMAND ${CMAKE_COMMAND} -P ${_filename} + USES_TERMINAL + BYPRODUCTS uninstall_byproduct + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" +) +set_property(SOURCE uninstall_byproduct PROPERTY SYMBOLIC 1) + +set_property(TARGET ${_uninstall} PROPERTY FOLDER "CMakePredefinedTargets") diff --git a/cmake/README.md b/cmake/README.md new file mode 100644 index 0000000..727e46a --- /dev/null +++ b/cmake/README.md @@ -0,0 +1,115 @@ +# A CMake Toolchain file for Windows MSVC and Windows Clang + +A CMake toolchain file describes the set of tools and utilities for compiling code in CMake. This repo provides +toolchains that describes how to compile using MSVC and Clang in CMake, with the goal of making Windows CMake builds more +canonical to reduce the 'barrier-to-entry' to build code for Windows. + +[![build status](https://github.com/MarkSchofield/Toolchain/actions/workflows/ci.yaml/badge.svg?branch=main)](https://github.com/MarkSchofield/Toolchain/actions/workflows/ci.yaml?query=branch%3Amain) + +## But I can build with MSVC in CMake already...? + +Yes, but you're probably either: + + 1. using a "Visual Studio Generator". In which case, CMake will emit - and then build - a Visual Studio Solution file + that knows how to find the build tools and utilities. + + 2. running from a Visual Studio "Command Prompt". In which case, the command prompt is hard-coded to use a set of + tools and utilities, which means that you always need to initialize your command prompt before getting started. + +By using a Toolchain file to describe the tools and utilities to build in CMake-terms, you can use other generators +(e.g. Ninja) and don't have to initialize an environment to run a build. + +## So this is a replacement for Visual Studio!? + +No, no. This uses the tools and utilities from installed Visual Studio but from a CMake build. + +## So this solves all my build problems!? + +No. This is an early attempt at making Visual Studio tooling easier to use from CMake. There's definitely gaps - +there's no support for "Windows Universal" builds, for example. But it gets enough of a stake-in-the-ground to start a +conversation. + +## How do I use this? + +Specify the 'Windows.MSVC.toolchain.cmake' or 'Windows.Clang.toolchain.cmake' file as a toolchain file to your CMake +builds. See [the documentation for CMake toolchains][cmake-toolchains] for more details on Toolchain files and how to +consume them. + +The ['Windows.MSVC.toolchain.cmake'](./Windows.MSVC.toolchain.cmake) and +['Windows.Clang.toolchain.cmake'](./Windows.Clang.toolchain.cmake) file has details on the various CMake variables +that can be used to configure the build. And [the example folder](./example) provides a CMake project that builds a +variety of Windows projects. + +## WindowsToolchain and VCPkg + +VCPkg - - is a Package Manager for C++. When using WindowsToolchain with VCPkg: + +1. The `vcpkg.cmake` script must be specified as the toolchain for CMake builds - as per [the VCPkg documentation](https://github.com/microsoft/vcpkg#getting-started). + +2. The path to the WindowsToolchain file - `Windows.MSVC.toolchain.cmake` - should be specified as the `VCPKG_CHAINLOAD_TOOLCHAIN_FILE`. The VCPkg toolchain will load the specified `VCPKG_CHAINLOAD_TOOLCHAIN_FILE`. + +Note: + +* VCPkg has default functionality to copy runtime dependencies from VCPkg-based packages during a CMake build. The functionality - implemented in [`applocal.ps1`](https://github.com/microsoft/vcpkg/blob/0ba60bfef5dea4cb2599daa7ad8364e309835a68/scripts/buildsystems/msbuild/applocal.ps1) - requires that `dumpbin`, `llvm-objdump` or `objdump` be found by PowerShell's `Get-Command`. On Windows, `applocal.ps1` is typically executed so that it would find `dumpbin` through the `PATH` environment variable, but since WindowsToolchain doesn't configure the `PATH` environment variable `applocal.ps1` fails. + + To disable this behavior, `VCPKG_APPLOCAL_DEPS` to `OFF` in your `CMakePresets.json`, on the CMake configuration command-line, or before the top-level `project()` call. If dependencies need to be copied during a build, use custom commands to copy them. For example, the following CMake snippet will copy dependencies for a target called `CommandLine`, for dependencies that support the `$` generator expression: + + ```cmake + if(WIN32) + add_custom_command(TARGET CommandLine POST_BUILD + COMMAND "${CMAKE_COMMAND};-E;$>,copy;$;$,true>" + COMMAND_EXPAND_LISTS + ) + endif() + ``` + + Note: The `$` generator expression requires CMake 3.21 or higher. + +## Linting + +WindowsToolchain uses [`cmakelang`][cmakelang] for linting the CMake files in the codebase. The +[.cmake-format.yaml](./.cmake-format.yaml) file describes the formatting style for the codebase. To run the linting +tools: + +1. Install [`cmakelang`][cmakelang] following [the installation instructions](https://cmake-format.readthedocs.io/en/latest/installation.html). +Note: Since WindowsToolchain uses a `.yaml` file for configuration, make sure to install the `cmakelang[YAML]` package. + +2. Run [`./analyze.ps1`](./analyze.ps1) + +The [Toolchain CI](.\.github\workflows\ci.yaml) GitHub Workflow enforces the linting rules during PR and CI. + +## Testing + +WindowsToolchain uses [Pester][pester] for testing the CMake files in the codebase. The tests are written for +[PowerShell Core][powershellcore] and checked into [the `Tests` folder](./Tests). To run the tests: + +1. Launch a [PowerShell Core][powershellcore] prompt. + + ```text + pwsh + ``` + +2. Make sure that you have [Pester][pester] installed. This only needs to be done once. + + ```powershell + Install-Module Pester + ``` + +3. Import the Pester module into the PowerShell Core session: + + ```powershell + Import-Module Pester + ``` + +4. Discover and run all tests: + + ```powershell + Invoke-Pester + ``` + +The [Toolchain CI](.\.github\workflows\ci.yaml) GitHub Workflow requires all tests to pass during PR and CI. + +[cmake-toolchains]: https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html "CMake Toolchains" +[cmakelang]: https://cmake-format.readthedocs.io/ "cmakelang" +[pester]: https://pester.dev/ "Pester" +[powershellcore]: https://learn.microsoft.com/en-us/powershell/ "PowerShell Core" diff --git a/cmake/VSWhere.cmake b/cmake/VSWhere.cmake new file mode 100644 index 0000000..d8f60e3 --- /dev/null +++ b/cmake/VSWhere.cmake @@ -0,0 +1,140 @@ +#---------------------------------------------------------------------------------------------------------------------- +# MIT License +# +# Copyright (c) 2021 Mark Schofield +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +#---------------------------------------------------------------------------------------------------------------------- +include_guard() + +#[[==================================================================================================================== + findVisualStudio + ---------------- + + Finds a Visual Studio instance, and sets CMake variables based on properties of the found instance. + + findVisualStudio( + [VERSION ] + [PRERELEASE ] + [PRODUCTS ] + [REQUIRES ...] + PROPERTIES + < > + ) +====================================================================================================================]] +# +function(findVisualStudio) + set(OPTIONS) + set(ONE_VALUE_KEYWORDS VERSION PRERELEASE PRODUCTS) + set(MULTI_VALUE_KEYWORDS REQUIRES PROPERTIES) + + cmake_parse_arguments( + PARSE_ARGV + 0 + FIND_VS + "${OPTIONS}" + "${ONE_VALUE_KEYWORDS}" + "${MULTI_VALUE_KEYWORDS}" + ) + + find_program( + VSWHERE_PATH + NAMES vswhere vswhere.exe + HINTS "$ENV{ProgramFiles\(x86\)}/Microsoft Visual Studio/Installer" + ) + + if(VSWHERE_PATH STREQUAL "VSWHERE_PATH-NOTFOUND") + message(FATAL_ERROR "'vswhere' isn't found.") + endif() + + set(VSWHERE_COMMAND ${VSWHERE_PATH} -latest) + + if(FIND_VS_PRERELEASE) + list(APPEND VSWHERE_COMMAND -prerelease) + endif() + + if(FIND_VS_PRODUCTS) + list( + APPEND + VSWHERE_COMMAND + -products + ${FIND_VS_PRODUCTS} + ) + endif() + + if(FIND_VS_REQUIRES) + list( + APPEND + VSWHERE_COMMAND + -requires + ${FIND_VS_REQUIRES} + ) + endif() + + if(FIND_VS_VERSION) + list( + APPEND + VSWHERE_COMMAND + -version + "${FIND_VS_VERSION}" + ) + endif() + + message(VERBOSE "findVisualStudio: VSWHERE_COMMAND = ${VSWHERE_COMMAND}") + + execute_process(COMMAND ${VSWHERE_COMMAND} OUTPUT_VARIABLE VSWHERE_OUTPUT) + + message(VERBOSE "findVisualStudio: VSWHERE_OUTPUT = ${VSWHERE_OUTPUT}") + + # Matches `VSWHERE_PROPERTY` in the `VSWHERE_OUTPUT` text in the format written by vswhere. + # The matched value is assigned to the variable `VARIABLE_NAME` in the parent scope. + function( + getVSWhereProperty + VSWHERE_OUTPUT + VSWHERE_PROPERTY + VARIABLE_NAME + ) + string( + REGEX MATCH + "${VSWHERE_PROPERTY}: [^\r\n]*" + VSWHERE_VALUE + "${VSWHERE_OUTPUT}" + ) + string( + REPLACE "${VSWHERE_PROPERTY}: " + "" + VSWHERE_VALUE + "${VSWHERE_VALUE}" + ) + set(${VARIABLE_NAME} + "${VSWHERE_VALUE}" + PARENT_SCOPE + ) + endfunction() + + while(FIND_VS_PROPERTIES) + list(POP_FRONT FIND_VS_PROPERTIES VSWHERE_PROPERTY) + list(POP_FRONT FIND_VS_PROPERTIES VSWHERE_CMAKE_VARIABLE) + getvswhereproperty("${VSWHERE_OUTPUT}" ${VSWHERE_PROPERTY} VSWHERE_VALUE) + set(${VSWHERE_CMAKE_VARIABLE} + ${VSWHERE_VALUE} + PARENT_SCOPE + ) + endwhile() +endfunction() diff --git a/cmake/Windows.Clang.toolchain.cmake b/cmake/Windows.Clang.toolchain.cmake new file mode 100644 index 0000000..b1274bb --- /dev/null +++ b/cmake/Windows.Clang.toolchain.cmake @@ -0,0 +1,123 @@ +#---------------------------------------------------------------------------------------------------------------------- +# MIT License +# +# Copyright (c) 2021 Mark Schofield +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +#---------------------------------------------------------------------------------------------------------------------- +# +# This CMake toolchain file configures a CMake, non-'Visual Studio Generator' build to use +# the Clang compilers and tools on Windows. +# +# The following variables can be used to configure the behavior of this toolchain file: +# +# | CMake Variable | Description | +# |---------------------------------------------|-----------------------------------------------------------------------------------------------------------------| +# | CMAKE_SYSTEM_VERSION | The version of the operating system for which CMake is to build. Defaults to '10.0.19041.0'. | +# | CMAKE_SYSTEM_PROCESSOR | The processor to compiler for. One of 'x86', 'x64', 'arm', 'arm64'. Defaults to ${CMAKE_HOST_SYSTEM_PROCESSOR}. | +# | CMAKE_WINDOWS_KITS_10_DIR | The location of the root of the Windows Kits 10 directory. | +# | CLANG_TIDY_CHECKS | List of rules clang-tidy should check. Defaults not set. | +# +# The toolchain file will set the following variables: +# +# | CMake Variable | Description | +# |---------------------------------------------|-------------------------------------------------------------------------------------------------------| +# | CMAKE_C_COMPILER | The path to the C compiler to use. | +# | CMAKE_CXX_COMPILER | The path to the C++ compiler to use. | +# | CMAKE_MT | The path to the 'mt.exe' tool to use. | +# | CMAKE_RC_COMPILER | The path tp the 'rc.exe' tool to use. | +# | CMAKE_SYSTEM_NAME | Windows | +# | WIN32 | 1 | +# | CMAKE_CXX_CLANG_TIDY | The commandline clang-tidy is used if CLANG_TIDY_CHECKS was set. | +# +# Resources: +# +# +cmake_minimum_required(VERSION 3.20) + +include_guard() + +if(NOT (CMAKE_HOST_SYSTEM_NAME STREQUAL Windows)) + return() +endif() + +set(UNUSED ${CMAKE_TOOLCHAIN_FILE}) # Note: only to prevent cmake unused variable warning +set(CMAKE_SYSTEM_NAME Windows) +set(CMAKE_TRY_COMPILE_PLATFORM_VARIABLES "CMAKE_SYSTEM_PROCESSOR;CMAKE_CROSSCOMPILING") +set(CMAKE_CROSSCOMPILING TRUE) +set(WIN32 1) + +if(NOT CMAKE_SYSTEM_PROCESSOR) + set(CMAKE_SYSTEM_PROCESSOR ${CMAKE_HOST_SYSTEM_PROCESSOR}) +endif() + +if(NOT CMAKE_VS_VERSION_RANGE) + set(CMAKE_VS_VERSION_RANGE "[16.0,)") +endif() + +if(NOT CMAKE_VS_VERSION_PRERELEASE) + set(CMAKE_VS_VERSION_PRERELEASE OFF) +endif() + +include("${CMAKE_CURRENT_LIST_DIR}/VSWhere.cmake") + +# Find Clang +# +findvisualstudio( + VERSION + ${CMAKE_VS_VERSION_RANGE} + PRERELEASE + ${CMAKE_VS_VERSION_PRERELEASE} + REQUIRES + Microsoft.VisualStudio.Component.VC.Llvm.Clang + PROPERTIES + installationVersion + VS_INSTALLATION_VERSION + installationPath + VS_INSTALLATION_PATH +) + +find_program( + CMAKE_C_COMPILER clang.exe HINTS "${VS_INSTALLATION_PATH}/VC/Tools/Llvm/x64/bin" "$ENV{ProgramFiles}/LLVM/bin" + REQUIRED +) + +find_program( + CMAKE_CXX_COMPILER clang++.exe HINTS "${VS_INSTALLATION_PATH}/VC/Tools/Llvm/x64/bin" "$ENV{ProgramFiles}/LLVM/bin" + REQUIRED +) + +set(CMAKE_CXX_COMPILER_ID "Clang") +set(CMAKE_CXX_COMPILER_FRONTEND_VARIANT "GNU") + +set(CMAKE_C_COMPILER_ID "Clang") +set(CMAKE_C_COMPILER_FRONTEND_VARIANT "GNU") + +if(CLANG_TIDY_CHECKS) + get_filename_component( + CLANG_PATH + ${CMAKE_CXX_COMPILER} + DIRECTORY + CACHE + ) + set(CMAKE_CXX_CLANG_TIDY "${CLANG_PATH}/clang-tidy.exe;-checks=${CLANG_TIDY_CHECKS}") +endif() + +# Windows Kits +include("${CMAKE_CURRENT_LIST_DIR}/Windows.Kits.cmake") diff --git a/cmake/Windows.Kits.cmake b/cmake/Windows.Kits.cmake new file mode 100644 index 0000000..3efc2b6 --- /dev/null +++ b/cmake/Windows.Kits.cmake @@ -0,0 +1,184 @@ +#---------------------------------------------------------------------------------------------------------------------- +# MIT License +# +# Copyright (c) 2021 Mark Schofield +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +#---------------------------------------------------------------------------------------------------------------------- +# +# | CMake Variable | Description | +# |-----------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------| +# | CMAKE_SYSTEM_VERSION | The version of the operating system for which CMake is to build. Defaults to the host version. | +# | CMAKE_VS_PLATFORM_TOOLSET_HOST_ARCHITECTURE | The architecture of the tooling to use. Defaults to x64. | +# | CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION | The version of the Windows SDK to use. Defaults to the highest installed, that is no higher than CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION_MAXIMUM | +# | CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION_MAXIMUM | The maximum version of the Windows SDK to use, for example '10.0.19041.0'. Defaults to nothing | +# | CMAKE_WINDOWS_KITS_10_DIR | The location of the root of the Windows Kits 10 directory. | +# +# The following variables will be set: +# +# | CMake Variable | Description | +# |---------------------------------------------|-------------------------------------------------------------------------------------------------------| +# | CMAKE_MT | The path to the 'mt' tool. | +# | CMAKE_RC_COMPILER | The path to the 'rc' tool. | +# | CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION | The version of the Windows SDK to be used. | +# | MDMERGE_TOOL | The path to the 'mdmerge' tool. | +# | MIDL_COMPILER | The path to the 'midl' compiler. | +# | WINDOWS_KITS_BIN_PATH | The path to the folder containing the Windows Kits binaries. | +# | WINDOWS_KITS_INCLUDE_PATH | The path to the folder containing the Windows Kits include files. | +# | WINDOWS_KITS_LIB_PATH | The path to the folder containing the Windows Kits library files. | +# | WINDOWS_KITS_REFERENCES_PATH | The path to the folder containing the Windows Kits references. | +# +include_guard() + +if(NOT CMAKE_SYSTEM_VERSION) + set(CMAKE_SYSTEM_VERSION ${CMAKE_HOST_SYSTEM_VERSION}) +endif() + +if(NOT CMAKE_VS_PLATFORM_TOOLSET_HOST_ARCHITECTURE) + set(CMAKE_VS_PLATFORM_TOOLSET_HOST_ARCHITECTURE x64) +endif() + +if(NOT CMAKE_WINDOWS_KITS_10_DIR) + get_filename_component( + CMAKE_WINDOWS_KITS_10_DIR + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows\\v10.0;InstallationFolder]" + ABSOLUTE + CACHE + ) +endif() + +if(NOT CMAKE_WINDOWS_KITS_10_DIR) + message(FATAL_ERROR "Unable to find an installed Windows SDK, and one wasn't specified.") +endif() + +# If a CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION wasn't specified, find the highest installed version that is no higher +# than the host version +if(NOT CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION) + file( + GLOB + WINDOWS_KITS_VERSIONS + RELATIVE + "${CMAKE_WINDOWS_KITS_10_DIR}/lib" + "${CMAKE_WINDOWS_KITS_10_DIR}/lib/*" + ) + list( + FILTER + WINDOWS_KITS_VERSIONS + INCLUDE + REGEX + "10\\.0\\." + ) + list( + SORT WINDOWS_KITS_VERSIONS + COMPARE NATURAL + ORDER DESCENDING + ) + while(WINDOWS_KITS_VERSIONS) + list(POP_FRONT WINDOWS_KITS_VERSIONS CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION) + if(NOT CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION_MAXIMUM) + message(VERBOSE "Windows.Kits: Defaulting version: ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}") + break() + endif() + + if(CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION VERSION_LESS_EQUAL CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION_MAXIMUM) + message(VERBOSE "Windows.Kits: Choosing version: ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}") + break() + endif() + + message(VERBOSE "Windows.Kits: Not suitable: ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}") + set(CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION) + endwhile() +endif() + +if(NOT CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION) + message(FATAL_ERROR "A Windows SDK could not be found.") +endif() + +set(WINDOWS_KITS_BIN_PATH + "${CMAKE_WINDOWS_KITS_10_DIR}/bin/${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}" + CACHE PATH "" FORCE +) +set(WINDOWS_KITS_INCLUDE_PATH + "${CMAKE_WINDOWS_KITS_10_DIR}/include/${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}" + CACHE PATH "" FORCE +) +set(WINDOWS_KITS_LIB_PATH + "${CMAKE_WINDOWS_KITS_10_DIR}/lib/${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}" + CACHE PATH "" FORCE +) +set(WINDOWS_KITS_REFERENCES_PATH + "${CMAKE_WINDOWS_KITS_10_DIR}/References" + CACHE PATH "" FORCE +) +set(WINDOWS_KITS_PLATFORM_PATH + "${CMAKE_WINDOWS_KITS_10_DIR}/Platforms/UAP/${CMAKE_SYSTEM_VERSION}/Platform.xml" + CACHE PATH "" FORCE +) + +if(NOT EXISTS ${WINDOWS_KITS_BIN_PATH}) + message( + FATAL_ERROR + "Windows SDK ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION} cannot be found: Folder '${WINDOWS_KITS_BIN_PATH}' does not exist." + ) +endif() + +if(NOT EXISTS ${WINDOWS_KITS_INCLUDE_PATH}) + message( + FATAL_ERROR + "Windows SDK ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION} cannot be found: Folder '${WINDOWS_KITS_INCLUDE_PATH}' does not exist." + ) +endif() + +if(NOT EXISTS ${WINDOWS_KITS_LIB_PATH}) + message( + FATAL_ERROR + "Windows SDK ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION} cannot be found: Folder '${WINDOWS_KITS_LIB_PATH}' does not exist." + ) +endif() + +set(CMAKE_MT "${WINDOWS_KITS_BIN_PATH}/${CMAKE_VS_PLATFORM_TOOLSET_HOST_ARCHITECTURE}/mt.exe") +set(CMAKE_RC_COMPILER_INIT "${WINDOWS_KITS_BIN_PATH}/${CMAKE_VS_PLATFORM_TOOLSET_HOST_ARCHITECTURE}/rc.exe") +set(CMAKE_RC_FLAGS_INIT "/nologo") + +set(MIDL_COMPILER "${WINDOWS_KITS_BIN_PATH}/${CMAKE_VS_PLATFORM_TOOLSET_HOST_ARCHITECTURE}/midl.exe") +set(MDMERGE_TOOL "${WINDOWS_KITS_BIN_PATH}/${CMAKE_VS_PLATFORM_TOOLSET_HOST_ARCHITECTURE}/mdmerge.exe") + +# Windows SDK +if((CMAKE_SYSTEM_PROCESSOR STREQUAL AMD64) OR (CMAKE_SYSTEM_PROCESSOR STREQUAL x64)) + set(WINDOWS_KITS_TARGET_ARCHITECTURE x64) +elseif( + (CMAKE_SYSTEM_PROCESSOR STREQUAL arm) + OR (CMAKE_SYSTEM_PROCESSOR STREQUAL arm64) + OR (CMAKE_SYSTEM_PROCESSOR STREQUAL x86) +) + set(WINDOWS_KITS_TARGET_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR}) +else() + message( + FATAL_ERROR "Unable identify Windows Kits architecture for CMAKE_SYSTEM_PROCESSOR ${CMAKE_SYSTEM_PROCESSOR}" + ) +endif() + +include_directories(SYSTEM "${WINDOWS_KITS_INCLUDE_PATH}/ucrt") +include_directories(SYSTEM "${WINDOWS_KITS_INCLUDE_PATH}/shared") +include_directories(SYSTEM "${WINDOWS_KITS_INCLUDE_PATH}/um") +include_directories(SYSTEM "${WINDOWS_KITS_INCLUDE_PATH}/winrt") +include_directories(SYSTEM "${WINDOWS_KITS_INCLUDE_PATH}/cppwinrt") +link_directories("${WINDOWS_KITS_LIB_PATH}/ucrt/${WINDOWS_KITS_TARGET_ARCHITECTURE}") +link_directories("${WINDOWS_KITS_LIB_PATH}/um/${WINDOWS_KITS_TARGET_ARCHITECTURE}") +link_directories("${WINDOWS_KITS_REFERENCES_PATH}/${WINDOWS_KITS_TARGET_ARCHITECTURE}") diff --git a/cmake/Windows.MSVC.toolchain.cmake b/cmake/Windows.MSVC.toolchain.cmake new file mode 100644 index 0000000..4437226 --- /dev/null +++ b/cmake/Windows.MSVC.toolchain.cmake @@ -0,0 +1,229 @@ +#---------------------------------------------------------------------------------------------------------------------- +# MIT License +# +# Copyright (c) 2021 Mark Schofield +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +#---------------------------------------------------------------------------------------------------------------------- +# +# This CMake toolchain file configures a CMake, non-'Visual Studio Generator' build to use +# the MSVC compilers and tools. +# +# The following variables can be used to configure the behavior of this toolchain file: +# +# | CMake Variable | Description | +# |---------------------------------------------|--------------------------------------------------------------------------------------------------------------------------| +# | CMAKE_SYSTEM_VERSION | The version of the operating system for which CMake is to build. Defaults to the host version. | +# | CMAKE_SYSTEM_PROCESSOR | The processor to compiler for. One of 'x86', 'x64'/'AMD64', 'arm', 'arm64'. Defaults to ${CMAKE_HOST_SYSTEM_PROCESSOR}. | +# | CMAKE_VS_VERSION_RANGE | A version range for VS instances to find. For example, '[16.0,17.0)' will find versions '16.*'. Defaults to '[16.0,17.0)' | +# | CMAKE_VS_VERSION_PRERELEASE | Whether 'prerelease' versions of Visual Studio should be considered. Defaults to 'OFF' | +# | CMAKE_VS_PRODUCTS | One or more Visual Studio Product IDs to consider. Defaults to '*' | +# | CMAKE_VS_PLATFORM_TOOLSET_VERSION | The version of the MSVC toolset to use. For example, 14.29.30133. Defaults to the highest available. | +# | CMAKE_VS_PLATFORM_TOOLSET_HOST_ARCHITECTURE | The architecture of the toolset to use. Defaults to 'x64'. | +# | CMAKE_WINDOWS_KITS_10_DIR | The location of the root of the Windows Kits 10 directory. | +# | VS_EXPERIMENTAL_MODULE | Whether experimental module support should be enabled. +# | VS_USE_SPECTRE_MITIGATION_RUNTIME | Whether the compiler should link with a runtime that uses 'Spectre' mitigations. Defaults to 'OFF'. | +# +# The toolchain file will set the following variables: +# +# | CMake Variable | Description | +# |---------------------------------------------|-------------------------------------------------------------------------------------------------------| +# | CMAKE_C_COMPILER | The path to the C compiler to use. | +# | CMAKE_CXX_COMPILER | The path to the C++ compiler to use. | +# | CMAKE_MT | The path to the 'mt.exe' tool to use. | +# | CMAKE_RC_COMPILER | The path tp the 'rc.exe' tool to use. | +# | CMAKE_SYSTEM_NAME | Windows | +# | WIN32 | 1 | +# | MSVC | 1 | +# | MSVC_VERSION | The '' version of the C++ compiler being used. For example, '1929' | +# +# Resources: +# +# +cmake_minimum_required(VERSION 3.20) + +include_guard() + +if(NOT (CMAKE_HOST_SYSTEM_NAME STREQUAL Windows)) + return() +endif() + +set(UNUSED ${CMAKE_TOOLCHAIN_FILE}) # Note: only to prevent cmake unused variable warning +set(CMAKE_SYSTEM_NAME Windows) +set(CMAKE_TRY_COMPILE_PLATFORM_VARIABLES + CMAKE_SYSTEM_PROCESSOR + CMAKE_CROSSCOMPILING + CMAKE_VS_VERSION_PRERELEASE + CMAKE_VS_VERSION_RANGE + CMAKE_VS_PRODUCTS + CMAKE_VS_PLATFORM_TOOLSET_HOST_ARCHITECTURE + VS_INSTALLATION_VERSION + VS_INSTALLATION_PATH +) +set(CMAKE_CROSSCOMPILING TRUE) +set(WIN32 1) +set(MSVC 1) + +include("${CMAKE_CURRENT_LIST_DIR}/VSWhere.cmake") + +if(NOT CMAKE_SYSTEM_PROCESSOR) + set(CMAKE_SYSTEM_PROCESSOR ${CMAKE_HOST_SYSTEM_PROCESSOR}) +endif() + +if(NOT CMAKE_VS_VERSION_RANGE) + set(CMAKE_VS_VERSION_RANGE "[16.0,)") +endif() + +if(NOT CMAKE_VS_VERSION_PRERELEASE) + set(CMAKE_VS_VERSION_PRERELEASE OFF) +endif() + +if(NOT CMAKE_VS_PRODUCTS) + set(CMAKE_VS_PRODUCTS "*") +endif() + +if(NOT CMAKE_VS_PLATFORM_TOOLSET_HOST_ARCHITECTURE) + set(CMAKE_VS_PLATFORM_TOOLSET_HOST_ARCHITECTURE x64) +endif() + +if(NOT VS_USE_SPECTRE_MITIGATION_RUNTIME) + set(VS_USE_SPECTRE_MITIGATION_RUNTIME OFF) +endif() + +# Find Visual Studio +# +findvisualstudio( + VERSION + ${CMAKE_VS_VERSION_RANGE} + PRERELEASE + ${CMAKE_VS_VERSION_PRERELEASE} + PRODUCTS + ${CMAKE_VS_PRODUCTS} + PROPERTIES + installationVersion + VS_INSTALLATION_VERSION + installationPath + VS_INSTALLATION_PATH +) + +message(VERBOSE "VS_INSTALLATION_VERSION = ${VS_INSTALLATION_VERSION}") +message(VERBOSE "VS_INSTALLATION_PATH = ${VS_INSTALLATION_PATH}") + +if(NOT VS_INSTALLATION_PATH) + message(FATAL_ERROR "Unable to find Visual Studio") +endif() + +cmake_path(NORMAL_PATH VS_INSTALLATION_PATH) + +set(VS_MSVC_PATH "${VS_INSTALLATION_PATH}/VC/Tools/MSVC") + +if(NOT VS_PLATFORM_TOOLSET_VERSION) + file( + GLOB + VS_TOOLSET_VERSIONS + RELATIVE + ${VS_MSVC_PATH} + ${VS_MSVC_PATH}/* + ) + list( + SORT VS_TOOLSET_VERSIONS + COMPARE NATURAL + ORDER DESCENDING + ) + list(POP_FRONT VS_TOOLSET_VERSIONS VS_TOOLSET_VERSION) +endif() + +set(VS_TOOLSET_PATH "${VS_INSTALLATION_PATH}/VC/Tools/MSVC/${VS_TOOLSET_VERSION}") + +# Set the tooling variables, include_directories and link_directories +# +function(getMsvcVersion COMPILER MSVC_VERSION_OUTPUT) + execute_process( + COMMAND "${COMPILER}" -Bv + ERROR_VARIABLE COMPILER_OUTPUT + OUTPUT_QUIET + ) + + if(COMPILER_OUTPUT MATCHES "cl.exe.*(([0-9]+)\\.([0-9]+)\\.([0-9]+)(\\.([0-9]+))?)") + set(COMPILER_VERSION ${CMAKE_MATCH_1}) + set(COMPILER_VERSION_MAJOR ${CMAKE_MATCH_2}) + set(COMPILER_VERSION_MINOR ${CMAKE_MATCH_3}) + endif() + + set(${MSVC_VERSION_OUTPUT} + "${COMPILER_VERSION_MAJOR}${COMPILER_VERSION_MINOR}" + PARENT_SCOPE + ) +endfunction() + +# Map CMAKE_SYSTEM_PROCESSOR values to CMAKE_VS_PLATFORM_TOOLSET_ARCHITECTURE that identifies the tools that should +# be used to produce code for the CMAKE_SYSTEM_PROCESSOR. +if((CMAKE_SYSTEM_PROCESSOR STREQUAL AMD64) OR (CMAKE_SYSTEM_PROCESSOR STREQUAL x64)) + set(CMAKE_VS_PLATFORM_TOOLSET_ARCHITECTURE x64) +elseif( + (CMAKE_SYSTEM_PROCESSOR STREQUAL arm) + OR (CMAKE_SYSTEM_PROCESSOR STREQUAL arm64) + OR (CMAKE_SYSTEM_PROCESSOR STREQUAL x86) +) + set(CMAKE_VS_PLATFORM_TOOLSET_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR}) +else() + message(FATAL_ERROR "Unable identify compiler architecture for CMAKE_SYSTEM_PROCESSOR ${CMAKE_SYSTEM_PROCESSOR}") +endif() + +set(CMAKE_CXX_COMPILER + "${VS_TOOLSET_PATH}/bin/Host${CMAKE_VS_PLATFORM_TOOLSET_HOST_ARCHITECTURE}/${CMAKE_VS_PLATFORM_TOOLSET_ARCHITECTURE}/cl.exe" +) +set(CMAKE_C_COMPILER + "${VS_TOOLSET_PATH}/bin/Host${CMAKE_VS_PLATFORM_TOOLSET_HOST_ARCHITECTURE}/${CMAKE_VS_PLATFORM_TOOLSET_ARCHITECTURE}/cl.exe" +) + +if(CMAKE_SYSTEM_PROCESSOR STREQUAL arm) + set(CMAKE_CXX_FLAGS_INIT "${CMAKE_CXX_FLAGS_INIT} /EHsc") +endif() + +getmsvcversion(${CMAKE_CXX_COMPILER} MSVC_VERSION) +if(NOT MSVC_VERSION) + message(FATAL_ERROR "Unable to obtain the compiler version from: ${CMAKE_CXX_COMPILER}") +endif() + +# Compiler +include_directories(SYSTEM "${VS_TOOLSET_PATH}/ATLMFC/include") +include_directories(SYSTEM "${VS_TOOLSET_PATH}/include") + +if(VS_USE_SPECTRE_MITIGATION_RUNTIME) + set(TOOLCHAIN_SPECTRE_TOKEN "/spectre") +else() + set(TOOLCHAIN_SPECTRE_TOKEN) +endif() + +link_directories("${VS_TOOLSET_PATH}/ATLMFC/lib${TOOLCHAIN_SPECTRE_TOKEN}/${CMAKE_VS_PLATFORM_TOOLSET_ARCHITECTURE}") +link_directories("${VS_TOOLSET_PATH}/lib${TOOLCHAIN_SPECTRE_TOKEN}/${CMAKE_VS_PLATFORM_TOOLSET_ARCHITECTURE}") + +link_directories("${VS_TOOLSET_PATH}/lib/x86/store/references") + +# Module support +if(VS_EXPERIMENTAL_MODULE) + add_compile_options(/experimental:module) + add_compile_options(/stdIfcDir "${VS_TOOLSET_PATH}/ifc/${CMAKE_VS_PLATFORM_TOOLSET_ARCHITECTURE}") +endif() + +# Windows Kits +include("${CMAKE_CURRENT_LIST_DIR}/Windows.Kits.cmake") + +set(TOOLCHAIN_SPECTRE_TOKEN)