mirror of
https://github.com/build-cpp/cmkr
synced 2026-06-08 13:22:55 +00:00
Compare commits
74 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7b1d33395e | |||
| 240fafc102 | |||
| 7a7ddf2699 | |||
| 605a04e72d | |||
| fd7f078127 | |||
| ee0e0d71d9 | |||
| 9d6897b572 | |||
| f8c34e5545 | |||
| e6d783ea20 | |||
| ee7fe38a7b | |||
| 66b2aad843 | |||
| d2e376080a | |||
| 634908c651 | |||
| 33911f3f1b | |||
| e650073bef | |||
| 96e44f7771 | |||
| cb9ad5cdb3 | |||
| c785b540d3 | |||
| 275b73eefa | |||
| ea49a3acb7 | |||
| 257e14c869 | |||
| 7508816fe7 | |||
| 45678bc5bc | |||
| b869e5646f | |||
| a499df84b1 | |||
| 49440f99e0 | |||
| 0c19c3da0c | |||
| 233cadadd0 | |||
| 58a5a935e5 | |||
| e69427bf94 | |||
| bc6359805d | |||
| 8dcc11e349 | |||
| 51dc49e6f1 | |||
| 7d27c28b59 | |||
| ae6bea3b58 | |||
| 5a0eda7abc | |||
| f17923c18b | |||
| ad17890e23 | |||
| c0b98b3ef4 | |||
| 2c6fc569af | |||
| 12ee23a44c | |||
| 82342a6b6f | |||
| 1d95fab0ce | |||
| 44a77329bf | |||
| e68c5fccdc | |||
| e2722d2c09 | |||
| 2627864383 | |||
| 05e21f734a | |||
| 1fd18503cd | |||
| 8b19441d34 | |||
| 2437eb90fe | |||
| 6e4006ec07 | |||
| 697b638723 | |||
| 109c4bc99f | |||
| cad85f0bb1 | |||
| 53c88ceafa | |||
| d2aa15b1c7 | |||
| e6ded077cf | |||
| 3615ccab94 | |||
| 73622aa5ba | |||
| 69d844a152 | |||
| af9a117f50 | |||
| 5fe8220728 | |||
| 28f541cd3c | |||
| 2f3fd7b95e | |||
| 07d99c582d | |||
| 5af2385cc3 | |||
| 981c48dbc5 | |||
| 153bdbe591 | |||
| 97f1c5c1cc | |||
| 5b6d9c3826 | |||
| a90988b81a | |||
| 3a1298f4e8 | |||
| 534f955827 |
+2
-2
@@ -10,7 +10,7 @@ AlignTrailingComments: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: true
|
||||
AllowShortFunctionsOnASingleLine: false
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
@@ -72,7 +72,7 @@ PenaltyExcessCharacter: 1000000
|
||||
PenaltyReturnTypeOnItsOwnLine: 60
|
||||
PointerAlignment: Right
|
||||
ReflowComments: true
|
||||
SortIncludes: true
|
||||
SortIncludes: false
|
||||
SortUsingDeclarations: true
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
|
||||
@@ -10,12 +10,19 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-2019, macos-10.15, ubuntu-20.04]
|
||||
os: [windows-2022, macos-11, ubuntu-20.04]
|
||||
env:
|
||||
BUILD_TYPE: Release
|
||||
BUILD_TYPE: 'Release'
|
||||
CMAKE_GENERATOR: 'Ninja'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Ninja
|
||||
uses: seanmiddleditch/gha-setup-ninja@6263846cf3c17009dfc81604efabae16044fc074 # master
|
||||
|
||||
- name: Visual Studio Development Environment
|
||||
uses: ilammy/msvc-dev-cmd@cec98b9d092141f74527d0afa6feb2af698cfe89 # v1.12.1
|
||||
|
||||
- name: Tag cmkr.cmake
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
@@ -38,28 +45,31 @@ jobs:
|
||||
ctest -C ${{ env.BUILD_TYPE }} --verbose
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ github.event.repository.name }}-${{ matrix.os }}
|
||||
path: install/bin/*
|
||||
|
||||
- name: Get lowercase OS name
|
||||
id: osname
|
||||
uses: ASzc/change-string-case-action@v1
|
||||
uses: ASzc/change-string-case-action@07c1e24a97f0951e13f88870b99c058fcf0b14cf # v5
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
with:
|
||||
string: ${{ runner.os }}
|
||||
|
||||
- name: Compress artifacts
|
||||
uses: vimtor/action-zip@v1
|
||||
uses: vimtor/action-zip@26a249fb00d43ca98dad77a4b3838025fc226aa1 # v1.1
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
with:
|
||||
files: install/bin/
|
||||
dest: ${{ github.event.repository.name }}-${{ steps.osname.outputs.lowercase }}.zip
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v0.1.15
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
with:
|
||||
prerelease: ${{ !startsWith(github.ref, 'refs/tags/v') || contains(github.ref, '-pre') }}
|
||||
files: ${{ github.event.repository.name }}-${{ steps.osname.outputs.lowercase }}.zip
|
||||
generate_release_notes: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
name: lint
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: clang-format
|
||||
id: clang-format
|
||||
uses: DoozyX/clang-format-lint-action@c3b2c943e924028b93a707a5b1b017976ab8d50c # v0.15
|
||||
with:
|
||||
exclude: './third_party'
|
||||
extensions: 'c,h,cpp,hpp'
|
||||
clangFormatVersion: 15
|
||||
style: file
|
||||
|
||||
- name: clang-format instructions
|
||||
if: ${{ failure() && steps.clang-format.outcome == 'failure' }}
|
||||
run: |
|
||||
# Instructions for fixing the formatting errors
|
||||
echo -e "\n\033[0;31mTo fix the formatting, run:\nclang-format -style=file -i \$(git ls-files \"*.c\" \"*.h\" \"*.cpp\" \"*.hpp\")\033[0m\n"
|
||||
exit 1
|
||||
@@ -9,3 +9,5 @@ build*/
|
||||
.idea/
|
||||
cmake-build*/
|
||||
CMakeLists.txt.user
|
||||
.vscode/
|
||||
.DS_Store
|
||||
Generated
+15
-44
@@ -7,17 +7,14 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR)
|
||||
message(FATAL_ERROR "In-tree builds are not supported. Run CMake from a separate directory: cmake -B build")
|
||||
endif()
|
||||
|
||||
# Regenerate CMakeLists.txt automatically in the root project
|
||||
set(CMKR_ROOT_PROJECT OFF)
|
||||
if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
|
||||
set(CMKR_ROOT_PROJECT ON)
|
||||
|
||||
# Enable folder support
|
||||
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
|
||||
endif()
|
||||
|
||||
# Create a configure-time dependency on cmake.toml to improve IDE support
|
||||
if(CMKR_ROOT_PROJECT)
|
||||
# Create a configure-time dependency on cmake.toml to improve IDE support
|
||||
configure_file(cmake.toml cmake.toml COPYONLY)
|
||||
endif()
|
||||
|
||||
@@ -25,7 +22,7 @@ project(cmkr
|
||||
LANGUAGES
|
||||
CXX
|
||||
VERSION
|
||||
0.2.13
|
||||
0.2.23
|
||||
DESCRIPTION
|
||||
"CMakeLists generator from TOML"
|
||||
)
|
||||
@@ -33,7 +30,7 @@ project(cmkr
|
||||
include("cmake/generate_documentation.cmake")
|
||||
include("cmake/generate_resources.cmake")
|
||||
|
||||
# third_party
|
||||
# Subdirectory: third_party
|
||||
set(CMKR_CMAKE_FOLDER ${CMAKE_FOLDER})
|
||||
if(CMAKE_FOLDER)
|
||||
set(CMAKE_FOLDER "${CMAKE_FOLDER}/third_party")
|
||||
@@ -43,7 +40,7 @@ endif()
|
||||
add_subdirectory(third_party)
|
||||
set(CMAKE_FOLDER ${CMKR_CMAKE_FOLDER})
|
||||
|
||||
# tests
|
||||
# Subdirectory: tests
|
||||
set(CMKR_CMAKE_FOLDER ${CMAKE_FOLDER})
|
||||
if(CMAKE_FOLDER)
|
||||
set(CMAKE_FOLDER "${CMAKE_FOLDER}/tests")
|
||||
@@ -53,64 +50,35 @@ endif()
|
||||
add_subdirectory(tests)
|
||||
set(CMAKE_FOLDER ${CMKR_CMAKE_FOLDER})
|
||||
|
||||
|
||||
|
||||
# Target cmkr_generate_documentation
|
||||
set(CMKR_TARGET cmkr_generate_documentation)
|
||||
set(cmkr_generate_documentation_SOURCES "")
|
||||
|
||||
set(CMKR_SOURCES ${cmkr_generate_documentation_SOURCES})
|
||||
# Target: cmkr_generate_documentation
|
||||
add_library(cmkr_generate_documentation INTERFACE)
|
||||
|
||||
if(cmkr_generate_documentation_SOURCES)
|
||||
target_sources(cmkr_generate_documentation INTERFACE ${cmkr_generate_documentation_SOURCES})
|
||||
endif()
|
||||
|
||||
set(CMKR_TARGET cmkr_generate_documentation)
|
||||
generate_documentation()
|
||||
|
||||
unset(CMKR_TARGET)
|
||||
unset(CMKR_SOURCES)
|
||||
|
||||
# Target cmkr
|
||||
set(CMKR_TARGET cmkr)
|
||||
set(cmkr_SOURCES "")
|
||||
|
||||
list(APPEND cmkr_SOURCES
|
||||
# Target: cmkr
|
||||
set(cmkr_SOURCES
|
||||
"src/arguments.cpp"
|
||||
"src/build.cpp"
|
||||
"src/cmake_generator.cpp"
|
||||
"src/error.cpp"
|
||||
"src/help.cpp"
|
||||
"src/main.cpp"
|
||||
"src/project_parser.cpp"
|
||||
"include/arguments.hpp"
|
||||
"include/build.hpp"
|
||||
"include/cmake_generator.hpp"
|
||||
"include/error.hpp"
|
||||
"include/fs.hpp"
|
||||
"include/help.hpp"
|
||||
"include/literals.hpp"
|
||||
"include/project_parser.hpp"
|
||||
"cmake/cmkr.cmake"
|
||||
"cmake/version.hpp.in"
|
||||
)
|
||||
|
||||
list(APPEND cmkr_SOURCES
|
||||
cmake.toml
|
||||
)
|
||||
|
||||
set(CMKR_SOURCES ${cmkr_SOURCES})
|
||||
add_executable(cmkr)
|
||||
|
||||
if(cmkr_SOURCES)
|
||||
target_sources(cmkr PRIVATE ${cmkr_SOURCES})
|
||||
endif()
|
||||
|
||||
get_directory_property(CMKR_VS_STARTUP_PROJECT DIRECTORY ${PROJECT_SOURCE_DIR} DEFINITION VS_STARTUP_PROJECT)
|
||||
if(NOT CMKR_VS_STARTUP_PROJECT)
|
||||
set_property(DIRECTORY ${PROJECT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT cmkr)
|
||||
endif()
|
||||
|
||||
target_sources(cmkr PRIVATE ${cmkr_SOURCES})
|
||||
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${cmkr_SOURCES})
|
||||
|
||||
target_compile_features(cmkr PRIVATE
|
||||
@@ -129,10 +97,13 @@ target_link_libraries(cmkr PRIVATE
|
||||
nlohmann_json
|
||||
)
|
||||
|
||||
generate_resources(${CMKR_TARGET})
|
||||
get_directory_property(CMKR_VS_STARTUP_PROJECT DIRECTORY ${PROJECT_SOURCE_DIR} DEFINITION VS_STARTUP_PROJECT)
|
||||
if(NOT CMKR_VS_STARTUP_PROJECT)
|
||||
set_property(DIRECTORY ${PROJECT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT cmkr)
|
||||
endif()
|
||||
|
||||
unset(CMKR_TARGET)
|
||||
unset(CMKR_SOURCES)
|
||||
set(CMKR_TARGET cmkr)
|
||||
generate_resources(${CMKR_TARGET})
|
||||
|
||||
install(
|
||||
TARGETS
|
||||
|
||||
@@ -13,27 +13,29 @@ type = "executable"
|
||||
sources = ["src/main.cpp"]
|
||||
```
|
||||
|
||||
`cmkr` can bootstrap itself from CMake and you only need CMake to use it.
|
||||
`cmkr` can bootstrap itself and you only need CMake and a C++ compiler to use it.
|
||||
|
||||
## Getting started
|
||||
|
||||
To get started run the following commands from your project directory:
|
||||
To get started, run the following commands from your project directory:
|
||||
|
||||
```sh
|
||||
curl https://raw.githubusercontent.com/build-cpp/cmkr/main/cmake/cmkr.cmake -o cmkr.cmake
|
||||
cmake -P cmkr.cmake
|
||||
```
|
||||
|
||||
After the bootstrapping process finishes, modify [`cmake.toml`](https://build-cpp.github.io/cmkr/cmake-toml) and open the project in your favorite IDE or build with CMake:
|
||||
After the bootstrapping process finishes, customize [`cmake.toml`](https://build-cpp.github.io/cmkr/cmake-toml) for your project and run CMake:
|
||||
|
||||
```sh
|
||||
cmake -B build
|
||||
cmake --build build
|
||||
```
|
||||
|
||||
Once bootstrapped, `cmkr` does not introduce extra steps to your workflow. After modifying `cmake.toml` you simply build/configure your CMake project and `cmkr` will automatically regenerate `CMakeLists.txt`.
|
||||
Once bootstrapped, `cmkr` does not introduce extra steps to your workflow. After modifying `cmake.toml` you simply build/configure your CMake project and `cmkr` will automatically regenerate `CMakeLists.txt` when necessary.
|
||||
|
||||
In CI settings the `cmkr` bootstrapping process is skipped so there is no extra configure-time overhead in your pipelines.
|
||||
<sub>**Note**: The `cmake.toml` project file, generated `CMakeLists.txt` and `cmkr.cmake` bootstrapping script are all intended to be added to source control.</sub>
|
||||
|
||||
In CI environments the `cmkr` bootstrapping process is skipped, so there is no additional overhead in your pipelines.
|
||||
|
||||
## Template repositories
|
||||
|
||||
@@ -53,10 +55,10 @@ Optionally you can put a [`cmkr` release](https://github.com/build-cpp/cmkr/rele
|
||||
```
|
||||
Usage: cmkr [arguments]
|
||||
arguments:
|
||||
init [executable|library|shared|static|interface] Starts a new project in the same directory.
|
||||
init [executable|library|shared|static|interface] Create a project.
|
||||
gen Generates CMakeLists.txt file.
|
||||
build <extra cmake args> Run cmake and build.
|
||||
install Run cmake --install. Needs admin privileges.
|
||||
install Run cmake --install.
|
||||
clean Clean the build directory.
|
||||
help Show help.
|
||||
version Current cmkr version.
|
||||
@@ -64,10 +66,10 @@ arguments:
|
||||
|
||||
## Credits
|
||||
|
||||
- https://github.com/gulrak/filesystem
|
||||
- https://github.com/Tessil/ordered-map
|
||||
- https://github.com/ToruNiina/toml11
|
||||
- https://github.com/mpark/variant
|
||||
- https://www.svgrepo.com/svg/192268/hammer
|
||||
- https://github.com/can1357 for buying `cmkr.build` ❤️
|
||||
- https://github.com/JustasMasiulis for fixing the dark theme ❤️
|
||||
- [gulrak/filesystem](https://github.com/gulrak/filesystem)
|
||||
- [Tessil/ordered-map](https://github.com/Tessil/ordered-map)
|
||||
- [ToruNiina/toml11](https://github.com/ToruNiina/toml11)
|
||||
- [mpark/variant](https://github.com/mpark/variant)
|
||||
- [SVG Repo Hammer](https://www.svgrepo.com/svg/192268/hammer)
|
||||
- [can1357](https://github.com/can1357) for buying `cmkr.build` ❤️
|
||||
- [JustasMasiulis](https://github.com/JustasMasiulis) for fixing the dark theme ❤️
|
||||
|
||||
+2
-2
@@ -4,14 +4,14 @@ cmkr-include = false
|
||||
|
||||
[project]
|
||||
name = "cmkr"
|
||||
version = "0.2.13"
|
||||
version = "0.2.23"
|
||||
description = "CMakeLists generator from TOML"
|
||||
languages = ["CXX"]
|
||||
subdirs = ["third_party", "tests"]
|
||||
include-after = [
|
||||
"cmake/generate_documentation.cmake",
|
||||
"cmake/generate_resources.cmake"
|
||||
]
|
||||
subdirs = ["third_party", "tests"]
|
||||
|
||||
[target.cmkr_generate_documentation]
|
||||
type = "interface"
|
||||
|
||||
+22
-5
@@ -2,7 +2,7 @@ include_guard()
|
||||
|
||||
# Change these defaults to point to your infrastructure if desired
|
||||
set(CMKR_REPO "https://github.com/build-cpp/cmkr" CACHE STRING "cmkr git repository" FORCE)
|
||||
set(CMKR_TAG "v0.2.13" CACHE STRING "cmkr git tag (this needs to be available forever)" FORCE)
|
||||
set(CMKR_TAG "v0.2.23" CACHE STRING "cmkr git tag (this needs to be available forever)" FORCE)
|
||||
set(CMKR_COMMIT_HASH "" CACHE STRING "cmkr git commit hash (optional)" FORCE)
|
||||
|
||||
# To bootstrap/generate a cmkr project: cmake -P cmkr.cmake
|
||||
@@ -58,7 +58,7 @@ else()
|
||||
endif()
|
||||
|
||||
# Use cached cmkr if found
|
||||
if(DEFINED ENV{CMKR_CACHE} AND EXISTS "$ENV{CMKR_CACHE}")
|
||||
if(DEFINED ENV{CMKR_CACHE})
|
||||
set(CMKR_DIRECTORY_PREFIX "$ENV{CMKR_CACHE}")
|
||||
string(REPLACE "\\" "/" CMKR_DIRECTORY_PREFIX "${CMKR_DIRECTORY_PREFIX}")
|
||||
if(NOT CMKR_DIRECTORY_PREFIX MATCHES "\\/$")
|
||||
@@ -72,10 +72,27 @@ endif()
|
||||
set(CMKR_DIRECTORY "${CMKR_DIRECTORY_PREFIX}${CMKR_TAG}")
|
||||
set(CMKR_CACHED_EXECUTABLE "${CMKR_DIRECTORY}/bin/${CMKR_EXECUTABLE_NAME}")
|
||||
|
||||
# Helper function to check if a string starts with a prefix
|
||||
# Cannot use MATCHES, see: https://github.com/build-cpp/cmkr/issues/61
|
||||
function(cmkr_startswith str prefix result)
|
||||
string(LENGTH "${prefix}" prefix_length)
|
||||
string(LENGTH "${str}" str_length)
|
||||
if(prefix_length LESS_EQUAL str_length)
|
||||
string(SUBSTRING "${str}" 0 ${prefix_length} str_prefix)
|
||||
if(prefix STREQUAL str_prefix)
|
||||
set("${result}" ON PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
endif()
|
||||
set("${result}" OFF PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Handle upgrading logic
|
||||
if(CMKR_EXECUTABLE AND NOT CMKR_CACHED_EXECUTABLE STREQUAL CMKR_EXECUTABLE)
|
||||
if(CMKR_EXECUTABLE MATCHES "^${CMAKE_CURRENT_BINARY_DIR}/_cmkr")
|
||||
if(DEFINED ENV{CMKR_CACHE} AND EXISTS "$ENV{CMKR_CACHE}")
|
||||
cmkr_startswith("${CMKR_EXECUTABLE}" "${CMAKE_CURRENT_BINARY_DIR}/_cmkr" CMKR_STARTSWITH_BUILD)
|
||||
cmkr_startswith("${CMKR_EXECUTABLE}" "${CMKR_DIRECTORY_PREFIX}" CMKR_STARTSWITH_CACHE)
|
||||
if(CMKR_STARTSWITH_BUILD)
|
||||
if(DEFINED ENV{CMKR_CACHE})
|
||||
message(AUTHOR_WARNING "[cmkr] Switching to cached cmkr: '${CMKR_CACHED_EXECUTABLE}'")
|
||||
if(EXISTS "${CMKR_CACHED_EXECUTABLE}")
|
||||
set(CMKR_EXECUTABLE "${CMKR_CACHED_EXECUTABLE}" CACHE FILEPATH "Full path to cmkr executable" FORCE)
|
||||
@@ -86,7 +103,7 @@ if(CMKR_EXECUTABLE AND NOT CMKR_CACHED_EXECUTABLE STREQUAL CMKR_EXECUTABLE)
|
||||
message(AUTHOR_WARNING "[cmkr] Upgrading '${CMKR_EXECUTABLE}' to '${CMKR_CACHED_EXECUTABLE}'")
|
||||
unset(CMKR_EXECUTABLE CACHE)
|
||||
endif()
|
||||
elseif(DEFINED ENV{CMKR_CACHE} AND EXISTS "$ENV{CMKR_CACHE}" AND CMKR_EXECUTABLE MATCHES "^${CMKR_DIRECTORY_PREFIX}")
|
||||
elseif(DEFINED ENV{CMKR_CACHE} AND CMKR_STARTSWITH_CACHE)
|
||||
message(AUTHOR_WARNING "[cmkr] Upgrading cached '${CMKR_EXECUTABLE}' to '${CMKR_CACHED_EXECUTABLE}'")
|
||||
unset(CMKR_EXECUTABLE CACHE)
|
||||
endif()
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
option(CMKR_GENERATE_DOCUMENTATION "Generate cmkr documentation" ${CMKR_ROOT_PROJECT})
|
||||
set(CMKR_TESTS "" CACHE INTERNAL "List of test directories in the order declared in tests/cmake.toml")
|
||||
|
||||
if(CMKR_GENERATE_DOCUMENTATION)
|
||||
# Hook the add_test function to capture the tests in the order declared in tests/cmake.toml
|
||||
function(add_test)
|
||||
cmake_parse_arguments(TEST "" "WORKING_DIRECTORY" "" ${ARGN})
|
||||
get_filename_component(TEST_WORKING_DIRECTORY "${TEST_WORKING_DIRECTORY}" NAME)
|
||||
list(APPEND CMKR_TESTS "${TEST_WORKING_DIRECTORY}")
|
||||
set(CMKR_TESTS "${CMKR_TESTS}" CACHE INTERNAL "")
|
||||
_add_test(${test} ${ARGN})
|
||||
endfunction()
|
||||
endif()
|
||||
|
||||
function(generate_documentation)
|
||||
if(CMKR_GENERATE_DOCUMENTATION)
|
||||
message(STATUS "[cmkr] Generating documentation...")
|
||||
|
||||
# Extract the order of the tests
|
||||
set(CMKR_TESTS "")
|
||||
file(READ "${PROJECT_SOURCE_DIR}/tests/cmake.toml" tests_toml NO_HEX_CONVERSION)
|
||||
string(REGEX MATCHALL "working-directory = \"([^\"]+)\"" tests_match "${tests_toml}")
|
||||
foreach(match ${tests_match})
|
||||
if(match MATCHES "working-directory = \"([^\"]+)\"")
|
||||
list(APPEND CMKR_TESTS "${CMAKE_MATCH_1}")
|
||||
else()
|
||||
message(FATAL_ERROR "This should not happen (wrong regex?)")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# Delete previously generated examples
|
||||
set(example_folder "${PROJECT_SOURCE_DIR}/docs/examples")
|
||||
|
||||
+19
-1
@@ -21,4 +21,22 @@ gh_edit_branch: "main"
|
||||
gh_edit_view_mode: "edit"
|
||||
gh_edit_source: docs
|
||||
|
||||
production_url : https://cmkr.build
|
||||
ga_tracking: G-DY172KY7Z9
|
||||
|
||||
production_url : https://cmkr.build
|
||||
footer_content: |
|
||||
<script src="https://giscus.app/client.js"
|
||||
data-repo="build-cpp/cmkr"
|
||||
data-repo-id="MDEwOlJlcG9zaXRvcnkyOTQwMDc3MzY="
|
||||
data-category="Documentation"
|
||||
data-category-id="DIC_kwDOEYYzuM4CTXeA"
|
||||
data-mapping="pathname"
|
||||
data-strict="1"
|
||||
data-reactions-enabled="1"
|
||||
data-emit-metadata="0"
|
||||
data-input-position="top"
|
||||
data-theme="preferred_color_scheme"
|
||||
data-lang="en"
|
||||
crossorigin="anonymous"
|
||||
async>
|
||||
</script>
|
||||
@@ -0,0 +1,76 @@
|
||||
---
|
||||
layout: page
|
||||
title: Basics
|
||||
nav_order: 1
|
||||
---
|
||||
|
||||
# Basics
|
||||
|
||||
To effectively use cmkr it helps to understand the basic concepts of CMake.
|
||||
|
||||
## Projects
|
||||
|
||||
A CMake **project** is a collection of targets. In the context of libraries the project usually corresponds to the **package** that other projects can depend on.
|
||||
|
||||
<sub>Visual Studio: a CMake **project** corresponds to a _solution_.</sub>
|
||||
|
||||
## Targets
|
||||
|
||||
The basic unit of CMake is called a **target**. A target (also referred to as [binary target](https://cmake.org/cmake/help/latest/manual/cmake-buildsystem.7.html#binary-targets) in the CMake documentation) corresponds to an executable or library you can build. There are also [pseudo targets](https://cmake.org/cmake/help/latest/manual/cmake-buildsystem.7.html#pseudo-targets), but we ignore them for now.
|
||||
|
||||
<sub>Visual Studio: a **target** corresponds to a _project_.</sub>
|
||||
|
||||
## Target Properties
|
||||
|
||||
Targets have a collection of **properties** that describe how to build them.
|
||||
|
||||
Examples of properties:
|
||||
|
||||
- _Sources_: the `*.cpp` files used to build the target.
|
||||
- _Compile options_: Command line flags for the compiler.
|
||||
- _Link libraries_: The **dependencies** required to build this target.
|
||||
|
||||
<sub>See the [CMake documentation](https://cmake.org/cmake/help/latest/manual/cmake-properties.7.html#properties-on-targets) for an exhaustive list of target properties.</sub>
|
||||
|
||||
**Important**: The term **link** has a slightly different meaning in CMake than you might expect. In addition to adding a library to the command line of the linker, CMake also propagates properties of the target you link to.
|
||||
|
||||
<sub>You can think of **linking** as _depending on_.</sub>
|
||||
|
||||
The propagation of properties depends on their **visibility**:
|
||||
|
||||
- **Private**: properties that are only used when building the target itself.
|
||||
- **Interface**: properties that are used when depending on this target.
|
||||
- **Public**: combination of private and interface.
|
||||
|
||||
In practice you default to **private**, unless consumers of your library _require_ the property to build their target. In that case you use **public**.
|
||||
|
||||
### Example
|
||||
|
||||
The most intuitive example is with _include directories_. Imagine there are two targets:
|
||||
|
||||
1. `StringUtils`: A library with string utilities.
|
||||
- _Sources_: `StringUtils/src/stringutils.cpp`
|
||||
- _Include directories_: `StringUtils/include`
|
||||
2. `DataProcessor`: An executable that uses functionality from `StringUtils` to process some data.
|
||||
- _Sources_: `DataProcessor/src/main.cpp`
|
||||
- _Link libraries_: `StringUtils`
|
||||
|
||||
The _include directories_ property of `StringUtils` has to be **public**. If it was **private**, the `DataProcessor` target would fail to `#include <stringutils.hpp>` since the _include directories_ property is not propagated.
|
||||
|
||||
The `cmake.toml` for this example would look something like this:
|
||||
|
||||
```toml
|
||||
[project]
|
||||
name = "DataProcessor"
|
||||
|
||||
[target.StringUtils]
|
||||
type = "static"
|
||||
sources = ["StringUtils/src/stringutils.cpp"]
|
||||
headers = ["StringUtils/include/stringutils.hpp"]
|
||||
include-directories = ["StringUtils/include"]
|
||||
|
||||
[target.DataProcessor]
|
||||
type = "executable"
|
||||
sources = ["DataProcessor/src/main.cpp"]
|
||||
link-libraries = ["StringUtils"]
|
||||
```
|
||||
+61
-6
@@ -2,14 +2,15 @@
|
||||
layout: page
|
||||
title: Reference
|
||||
permalink: /cmake-toml/
|
||||
nav_order: 1
|
||||
nav_order: 3
|
||||
---
|
||||
|
||||
# Reference
|
||||
|
||||
This page is a reference for the options in the `cmake.toml` file. If you think anything is missing or unclear, please [edit this page](https://github.com/build-cpp/cmkr/edit/main/docs/cmake-toml.md) or open an [issue](https://github.com/build-cpp/cmkr/issues).
|
||||
|
||||
See the [examples](/examples) section for concrete examples.
|
||||
This is a reference page. Check out the [examples](/examples) and the [cmkr topic](https://github.com/topics/cmkr) as well.
|
||||
{:.info}
|
||||
|
||||
## CMake configuration
|
||||
|
||||
@@ -27,6 +28,7 @@ name = "myproject"
|
||||
version = "1.0.0"
|
||||
description = "Description of the project"
|
||||
languages = ["C", "CXX"]
|
||||
msvc-runtime = "" # dynamic (implicit default), static
|
||||
cmake-before = """
|
||||
message(STATUS "CMake injected before the project() call")
|
||||
"""
|
||||
@@ -43,8 +45,8 @@ You can specify your own conditions and use them in any `condition` field:
|
||||
|
||||
```toml
|
||||
[conditions]
|
||||
arch64 = "CMAKE_SIZEOF_VOID_P EQUALS 8"
|
||||
arch32 = "CMAKE_SIZEOF_VOID_P EQUALS 4"
|
||||
arch64 = "CMAKE_SIZEOF_VOID_P EQUAL 8"
|
||||
arch32 = "CMAKE_SIZEOF_VOID_P EQUAL 4"
|
||||
```
|
||||
|
||||
This will make the `arch64` and `arch32` conditions available with their respective CMake expressions.
|
||||
@@ -58,6 +60,8 @@ sources = ["src/main.cpp"]
|
||||
windows.sources = ["src/windows_specific.cpp"]
|
||||
```
|
||||
|
||||
### Predefined conditions
|
||||
|
||||
The following conditions are predefined (you can override them if you desire):
|
||||
|
||||
```toml
|
||||
@@ -68,7 +72,7 @@ unix = "UNIX"
|
||||
bsd = "CMAKE_SYSTEM_NAME MATCHES \"BSD\""
|
||||
linux = "CMAKE_SYSTEM_NAME MATCHES \"Linux\""
|
||||
gcc = "CMAKE_CXX_COMPILER_ID STREQUAL \"GNU\" OR CMAKE_C_COMPILER_ID STREQUAL \"GNU\""
|
||||
clang = "CMAKE_CXX_COMPILER_ID MATCHES \"Clang\" OR CMAKE_C_COMPILER_ID MATCHES \"Clang\""
|
||||
clang = "(CMAKE_CXX_COMPILER_ID MATCHES \"Clang\" AND NOT CMAKE_CXX_COMPILER_FRONTEND_VARIANT MATCHES \"^MSVC$\") OR (CMAKE_C_COMPILER_ID MATCHES \"Clang\" AND NOT CMAKE_C_COMPILER_FRONTEND_VARIANT MATCHES \"^MSVC$\")"
|
||||
msvc = "MSVC"
|
||||
root = "CMKR_ROOT_PROJECT"
|
||||
x64 = "CMAKE_SIZEOF_VOID_P EQUAL 8"
|
||||
@@ -90,6 +94,29 @@ include-before = ["cmake/before-subdir.cmake"]
|
||||
include-after = ["cmake/after-subdir.cmake"]
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
```toml
|
||||
[options]
|
||||
MYPROJECT_BUILD_TESTS = false
|
||||
MYPROJECT_SPECIAL_OPTION = { value = true, help = "Docstring for this option." }
|
||||
MYPROJECT_BUILD_EXAMPLES = "root"
|
||||
```
|
||||
|
||||
Options correspond to [CMake cache variables](https://cmake.org/cmake/help/book/mastering-cmake/chapter/CMake%20Cache.html) that can be used to customize your project at configure-time. You can configure with `cmake -DMYPROJECT_BUILD_TESTS=ON` to enable the option. Every option automatically gets a corresponding [condition](#conditions).
|
||||
|
||||
The special value `root` can be used to set the option to `true` if the project is compiled as the root project (it will be `false` if someone is including your project via `[fetch-content]` or `[subdir]`).
|
||||
|
||||
## Variables
|
||||
|
||||
```toml
|
||||
[variables]
|
||||
MYBOOL = true
|
||||
MYSTRING = "hello"
|
||||
```
|
||||
|
||||
Variables emit a [`set`](https://cmake.org/cmake/help/latest/command/set.html) and can be used to configure subprojects and packages.
|
||||
|
||||
## Vcpkg
|
||||
|
||||
```toml
|
||||
@@ -123,6 +150,7 @@ components = ["mycomponent"]
|
||||
condition = "mycondition"
|
||||
git = "https://github.com/myuser/gitcontent"
|
||||
tag = "v0.1"
|
||||
shallow = false
|
||||
|
||||
[fetch-content.svncontent]
|
||||
condition = "mycondition"
|
||||
@@ -132,7 +160,10 @@ rev = "svn_rev"
|
||||
[fetch-content.urlcontent]
|
||||
condition = "mycondition"
|
||||
url = "https://content-host.com/urlcontent.zip"
|
||||
hash = "123123123123"
|
||||
# These are equivalent, supported algorithms:
|
||||
# md5, sha1, sha224, sha256, sha384, sha512, sha3_224, sha3_256, sha3_384, sha3_512
|
||||
hash = "SHA1 502a4e25b8b209889c99c7fa0732102682c2e4ff"
|
||||
sha1 = "502a4e25b8b209889c99c7fa0732102682c2e4ff"
|
||||
```
|
||||
|
||||
## Targets
|
||||
@@ -144,6 +175,7 @@ alias = "mytarget::mytarget"
|
||||
type = "static" # executable, shared (DLL), static, interface, object, library, custom
|
||||
headers = ["src/mytarget.h"]
|
||||
sources = ["src/mytarget.cpp"]
|
||||
msvc-runtime = "" # dynamic (implicit default), static
|
||||
|
||||
# The keys below match the target_xxx CMake commands
|
||||
# Keys prefixed with private- will get PRIVATE visibility
|
||||
@@ -180,6 +212,29 @@ CXX_STANDARD_REQUIRED = true
|
||||
FOLDER = "MyFolder"
|
||||
```
|
||||
|
||||
## Templates
|
||||
|
||||
To avoid repeating yourself you can create your own target type and use it in your targets:
|
||||
|
||||
```toml
|
||||
[template.example]
|
||||
condition = "MYPROJECT_BUILD_EXAMPLES"
|
||||
type = "executable"
|
||||
link-libraries = ["myproject::mylib"]
|
||||
add-function = ""
|
||||
pass-sources = false
|
||||
|
||||
# Properties from the template are merged with the ones here
|
||||
[target.myexample]
|
||||
type = "example"
|
||||
sources = ["src/myexample.cpp"]
|
||||
```
|
||||
|
||||
The properties declared on a `template` are the same as the ones you use for targets. The only exceptions are:
|
||||
|
||||
- `add-function`: Specifies a custom add function. Projects like [pybind11](https://pybind11.readthedocs.io/en/stable/cmake/index.html#new-findpython-mode) have their own `add_xxx` function, which you can specify here.
|
||||
- `pass-sources`: Pass sources directly to the add function instead of using `target_sources`.
|
||||
|
||||
## Tests and installation (unfinished)
|
||||
|
||||
**Note**: The `[[test]]` and `[[install]]` are unfinished features and will likely change in a future release.
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
# Automatically generated from tests/msvc-runtime/cmake.toml - DO NOT EDIT
|
||||
layout: default
|
||||
title: Static MSVC runtime
|
||||
permalink: /examples/msvc-runtime
|
||||
parent: Examples
|
||||
nav_order: 8
|
||||
---
|
||||
|
||||
# Static MSVC runtime
|
||||
|
||||
|
||||
|
||||
```toml
|
||||
[project]
|
||||
name = "msvc-runtime"
|
||||
description = "Static MSVC runtime"
|
||||
msvc-runtime = "static"
|
||||
|
||||
# This target will compile with a static runtime
|
||||
[target.static-runtime]
|
||||
type = "executable"
|
||||
sources = ["src/main.cpp"]
|
||||
|
||||
# This target overrides the [project].msvc-runtime
|
||||
[target.dynamic-runtime]
|
||||
type = "executable"
|
||||
sources = ["src/main.cpp"]
|
||||
msvc-runtime = "dynamic"
|
||||
```
|
||||
|
||||
|
||||
|
||||
<sup><sub>This page was automatically generated from [tests/msvc-runtime/cmake.toml](https://github.com/build-cpp/cmkr/tree/main/tests/msvc-runtime/cmake.toml).</sub></sup>
|
||||
@@ -19,7 +19,7 @@ description = "Dependencies from vcpkg"
|
||||
# See https://github.com/microsoft/vcpkg/releases for vcpkg versions
|
||||
# See https://vcpkg.io/en/packages.html for available packages
|
||||
[vcpkg]
|
||||
version = "2021.05.12"
|
||||
version = "2022.11.14"
|
||||
packages = ["fmt"]
|
||||
|
||||
[find-package]
|
||||
|
||||
+19
-15
@@ -1,12 +1,12 @@
|
||||
---
|
||||
layout: home
|
||||
title: Index
|
||||
title: Documentation
|
||||
nav_order: 0
|
||||
---
|
||||
|
||||
# Index
|
||||
# Documentation
|
||||
|
||||
`cmkr`, pronounced "cmaker", is a modern build system based on [CMake](https://cmake.org/) and [TOML](https://toml.io).
|
||||
[`cmkr`](https://github.com/build-cpp/cmkr), pronounced "cmaker", is a modern build system based on [CMake](https://cmake.org/) and [TOML](https://toml.io).
|
||||
|
||||
`cmkr` parses `cmake.toml` files and generates a modern, idiomatic `CMakeLists.txt` for you. A minimal example:
|
||||
|
||||
@@ -19,27 +19,29 @@ type = "executable"
|
||||
sources = ["src/main.cpp"]
|
||||
```
|
||||
|
||||
`cmkr` can bootstrap itself from CMake and you only need CMake to use it.
|
||||
`cmkr` can bootstrap itself, and you only need CMake and a C++ compiler to use it.
|
||||
|
||||
## Getting started
|
||||
|
||||
To get started run the following commands from your project directory:
|
||||
To get started, run the following commands from your project directory:
|
||||
|
||||
```sh
|
||||
curl https://raw.githubusercontent.com/build-cpp/cmkr/main/cmake/cmkr.cmake -o cmkr.cmake
|
||||
cmake -P cmkr.cmake
|
||||
```
|
||||
|
||||
After the bootstrapping process finishes, modify [`cmake.toml`](https://build-cpp.github.io/cmkr/cmake-toml) and open the project in your favorite IDE or build with CMake:
|
||||
After the bootstrapping process finishes, customize [`cmake.toml`](./cmake-toml) for your project and run CMake:
|
||||
|
||||
```sh
|
||||
cmake -B build
|
||||
cmake --build build
|
||||
```
|
||||
|
||||
Once bootstrapped, `cmkr` does not introduce extra steps to your workflow. After modifying `cmake.toml` you simply build/configure your CMake project and `cmkr` will automatically regenerate `CMakeLists.txt`.
|
||||
Once bootstrapped, `cmkr` does not introduce extra steps to your workflow. After modifying `cmake.toml` you simply build/configure your CMake project and `cmkr` will automatically regenerate `CMakeLists.txt` when necessary.
|
||||
|
||||
In CI settings the `cmkr` bootstrapping process is skipped so there is no extra configure-time overhead in your pipelines.
|
||||
<sub>**Note**: The `cmake.toml` project file, generated `CMakeLists.txt` and `cmkr.cmake` bootstrapping script are all intended to be added to source control.</sub>
|
||||
|
||||
In CI environments the `cmkr` bootstrapping process is skipped, so there is no additional overhead in your pipelines.
|
||||
|
||||
## Template repositories
|
||||
|
||||
@@ -59,10 +61,10 @@ Optionally you can put a [`cmkr` release](https://github.com/build-cpp/cmkr/rele
|
||||
```
|
||||
Usage: cmkr [arguments]
|
||||
arguments:
|
||||
init [executable|library|shared|static|interface] Starts a new project in the same directory.
|
||||
init [executable|library|shared|static|interface] Create a project.
|
||||
gen Generates CMakeLists.txt file.
|
||||
build <extra cmake args> Run cmake and build.
|
||||
install Run cmake --install. Needs admin privileges.
|
||||
install Run cmake --install.
|
||||
clean Clean the build directory.
|
||||
help Show help.
|
||||
version Current cmkr version.
|
||||
@@ -70,8 +72,10 @@ arguments:
|
||||
|
||||
## Credits
|
||||
|
||||
- https://github.com/gulrak/filesystem
|
||||
- https://github.com/Tessil/ordered-map
|
||||
- https://github.com/ToruNiina/toml11
|
||||
- https://github.com/mpark/variant
|
||||
- https://www.svgrepo.com/svg/192268/hammer
|
||||
- [gulrak/filesystem](https://github.com/gulrak/filesystem)
|
||||
- [Tessil/ordered-map](https://github.com/Tessil/ordered-map)
|
||||
- [ToruNiina/toml11](https://github.com/ToruNiina/toml11)
|
||||
- [mpark/variant](https://github.com/mpark/variant)
|
||||
- [SVG Repo Hammer](https://www.svgrepo.com/svg/192268/hammer)
|
||||
- [can1357](https://github.com/can1357) for buying `cmkr.build` ❤️
|
||||
- [JustasMasiulis](https://github.com/JustasMasiulis) for fixing the dark theme ❤️
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
---
|
||||
layout: page
|
||||
title: Philosophy
|
||||
nav_order: 2
|
||||
published: false
|
||||
---
|
||||
|
||||
# Philosophy
|
||||
|
||||
## Problem statement
|
||||
|
||||
Similar to writing good C++, writing good CMake is difficult. The main difference is that nobody actually wants to learn CMake. The build system is something that should "just work".
|
||||
|
||||
There have been many attempts at creating new build systems (with varying levels of success). Naturally this causes [competing standards](https://xkcd.com/927/), which is undesirable. CMake is pretty much the de facto standard, and it has seen extensive use in complex software projects.
|
||||
|
||||
One of the main issues of CMake is the turing-complete scripting language you use to describe your projects. As your project gets more complex this can be very helpful, but it also unnecessarily complicates simple projects. There have been discussions about a declarative language on the [CMake issue tracker](https://gitlab.kitware.com/cmake/cmake/-/issues/19891) to solve this problem, but it looks like a "LISP-like" language is seriously being considered...
|
||||
|
||||
## The solution
|
||||
|
||||
The way cmkr (pronounced "cmaker") solves this problem is by using [TOML](https://toml.io/). Below is a minimal `cmake.toml` project:
|
||||
|
||||
```toml
|
||||
[project]
|
||||
name = "cmkr_for_beginners"
|
||||
|
||||
[target.hello_world]
|
||||
type = "executable"
|
||||
sources = ["src/main.cpp"]
|
||||
```
|
||||
|
||||
The key difference between `cmkr` and other build systems is that it _generates_ CMake. This means that your projects are fully compatible with the CMake ecosystem, and you can try it out without having to rewrite your whole build system.
|
||||
|
||||
TODO: link/include more examples? Talk about conditions, packaging (missing)
|
||||
|
||||
### Another layer?!
|
||||
|
||||
A common issue people have with cmkr is that it introduces an additional layer of indirection to your build system. CMake is already a meta-buildsystem, which means you could call cmkr a "meta-meta-buildsystem".
|
||||
|
||||
Presumably the reason for this friction is that additional layers of abstraction introduce additional complexity. Because of this cmkr has been designed to be completely seamless:
|
||||
|
||||
- The user doesn't have to install any additional software to use cmkr. All you need is a semi-recent version of CMake and a C++ compiler.
|
||||
- Modifying `cmake.toml` automatically triggers a regeneration of `CMakeLists.txt`. There is no change to your build process.
|
||||
- The `CMakeLists.txt` is generated to be human-readable. This means you can easily "eject" from cmkr and go back to CMake.
|
||||
|
||||
An additional argument for cmkr is that anecdotally people say it "just works". Because of its simplicity it's also easy to teach, even to people without programming experience.
|
||||
|
||||
There is also precedent in the JavaScript community. Bundlers and translators are the norm there and their developer experience is miles ahead of C++.
|
||||
|
||||
<sub>Not to say the JavaScript ecosystem is without its flaws, but generators does not appear to be one of them</sub>
|
||||
|
||||
### Unsupported features
|
||||
|
||||
Because cmkr is still in early in development there are many missing/unfinished features. It was decided that users can bridge the gap by including CMake at arbitrary locations.
|
||||
|
||||
This has the advantage that it forces complexity in the build system to be self-contained and modular, a problem all too common in projects as they age.
|
||||
|
||||
## Enterprise
|
||||
|
||||
Words like "bootstrapping" and "generating code" might worry engineers working in an enterprise environment, and rightly so. From the beginning it has been a priority to make cmkr suitable for use in big corporations with strict protocols for security and reproducibility.
|
||||
|
||||
### No additional dependencies
|
||||
|
||||
As mentioned above, the only thing you need is a working C++ compiler and a semi-recent version of CMake. It is assumed that you are already building (and executing) C++ projects on your servers, so cmkr does not introduce additional requirements.
|
||||
|
||||
All the logic for downloading and compiling the `cmkr` executable is self-contained in a ~250 line `cmkr.cmake` script. You can easily audit it and see if it's up to your standards.
|
||||
|
||||
### Reproducibility
|
||||
|
||||
Per default the `cmkr.cmake` bootstrapping script contains the exact version of cmkr used to generate your project. As long as the cmkr repository is available you will build the exact same version of cmkr, which will generate the exact same `CMakeLists.txt` file.
|
||||
|
||||
This also means that cmkr can decide to break backwards compatibility without affecting legacy projects. An effort will always be made to maintain backwards compatibility though.
|
||||
|
||||
### Integrity
|
||||
|
||||
As an additional safeguard you can modify `cmkr.cmake` to pin the version tag to a commit hash. This hash is checked to ensure the integrity of the upstream repository.
|
||||
|
||||
### Availability
|
||||
|
||||
You can easily point `cmkr.cmake` to a mirror of the cmkr repository to ensure availability should something catastrophic happen.
|
||||
|
||||
### Not executed in CI
|
||||
|
||||
The final (and key) feature is that the bootstrapping process is never executed in CI environments. This means `cmkr` is only ever executed on your developer's machines and not on your infrastructure.
|
||||
@@ -1,2 +0,0 @@
|
||||
bundle exec just-the-docs rake search:init
|
||||
bundle exec jekyll serve --livereload
|
||||
Regular → Executable
+1
-1
@@ -1,2 +1,2 @@
|
||||
bundle install
|
||||
bundle exec jekyll serve --force_polling --livereload --incremental
|
||||
bundle exec jekyll serve --force_polling --livereload --incremental
|
||||
@@ -7,5 +7,3 @@ const char *handle_args(int argc, char **argv);
|
||||
|
||||
} // namespace args
|
||||
} // namespace cmkr
|
||||
|
||||
const char *cmkr_args_handle_args(int, char **);
|
||||
|
||||
@@ -11,9 +11,3 @@ int install();
|
||||
|
||||
} // namespace build
|
||||
} // namespace cmkr
|
||||
|
||||
int cmkr_build_run(int argc, char **argv);
|
||||
|
||||
int cmkr_build_clean();
|
||||
|
||||
int cmkr_build_install();
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace cmkr {
|
||||
namespace error {
|
||||
|
||||
struct Status {
|
||||
enum class Code {
|
||||
Success = 0,
|
||||
RuntimeError,
|
||||
InitError,
|
||||
GenerationError,
|
||||
BuildError,
|
||||
CleanError,
|
||||
InstallError,
|
||||
};
|
||||
Status(Code ec) noexcept;
|
||||
operator int() const noexcept;
|
||||
Code code() const noexcept;
|
||||
|
||||
private:
|
||||
Code ec_ = Code::Success;
|
||||
};
|
||||
|
||||
} // namespace error
|
||||
} // namespace cmkr
|
||||
|
||||
const char *cmkr_error_status_string(int);
|
||||
+1
-3
@@ -1,8 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || \
|
||||
(defined(__cplusplus) && __cplusplus >= 201703L)) && \
|
||||
defined(__has_include)
|
||||
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include)
|
||||
#if __has_include(<filesystem>) && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500)
|
||||
#define GHC_USE_STD_FS
|
||||
#include <filesystem>
|
||||
|
||||
@@ -9,7 +9,3 @@ const char *message() noexcept;
|
||||
|
||||
} // namespace help
|
||||
} // namespace cmkr
|
||||
|
||||
const char *cmkr_help_version(void);
|
||||
|
||||
const char *cmkr_help_message(void);
|
||||
|
||||
+29
-10
@@ -1,9 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <mpark/variant.hpp>
|
||||
#include <string>
|
||||
#include <tsl/ordered_map.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include <mpark/variant.hpp>
|
||||
#include <tsl/ordered_map.h>
|
||||
#include <tsl/ordered_set.h>
|
||||
|
||||
namespace cmkr {
|
||||
namespace parser {
|
||||
@@ -13,18 +15,18 @@ using Condition = tsl::ordered_map<std::string, T>;
|
||||
|
||||
using ConditionVector = Condition<std::vector<std::string>>;
|
||||
|
||||
struct Setting {
|
||||
struct Variable {
|
||||
std::string name;
|
||||
std::string comment;
|
||||
mpark::variant<bool, std::string> val;
|
||||
std::string help;
|
||||
mpark::variant<bool, std::string> value;
|
||||
bool cache = false;
|
||||
bool force = false;
|
||||
};
|
||||
|
||||
struct Option {
|
||||
std::string name;
|
||||
std::string comment;
|
||||
bool val = false;
|
||||
std::string help;
|
||||
mpark::variant<bool, std::string> value;
|
||||
};
|
||||
|
||||
struct Package {
|
||||
@@ -46,6 +48,10 @@ struct Vcpkg {
|
||||
};
|
||||
|
||||
std::vector<Package> packages;
|
||||
|
||||
bool enabled() const {
|
||||
return !packages.empty();
|
||||
}
|
||||
};
|
||||
|
||||
enum TargetType {
|
||||
@@ -67,7 +73,6 @@ struct Target {
|
||||
TargetType type = target_last;
|
||||
std::string type_name;
|
||||
|
||||
ConditionVector headers;
|
||||
ConditionVector sources;
|
||||
|
||||
// https://cmake.org/cmake/help/latest/manual/cmake-commands.7.html#project-commands
|
||||
@@ -152,7 +157,17 @@ struct Content {
|
||||
ConditionVector include_after;
|
||||
};
|
||||
|
||||
enum MsvcRuntimeType {
|
||||
msvc_dynamic,
|
||||
msvc_static,
|
||||
msvc_last,
|
||||
};
|
||||
|
||||
extern const char *msvcRuntimeTypeNames[msvc_last];
|
||||
|
||||
struct Project {
|
||||
const Project *parent;
|
||||
|
||||
// This is the CMake version required to use all cmkr versions.
|
||||
std::string cmake_version = "3.15";
|
||||
std::string cmkr_include = "cmkr.cmake";
|
||||
@@ -170,11 +185,13 @@ struct Project {
|
||||
std::string project_version;
|
||||
std::string project_description;
|
||||
std::vector<std::string> project_languages;
|
||||
bool project_allow_unknown_languages = false;
|
||||
MsvcRuntimeType project_msvc_runtime = msvc_last;
|
||||
Condition<std::string> cmake_before;
|
||||
Condition<std::string> cmake_after;
|
||||
ConditionVector include_before;
|
||||
ConditionVector include_after;
|
||||
std::vector<Setting> settings;
|
||||
std::vector<Variable> variables;
|
||||
std::vector<Option> options;
|
||||
std::vector<Package> packages;
|
||||
Vcpkg vcpkg;
|
||||
@@ -187,6 +204,8 @@ struct Project {
|
||||
std::vector<Subdir> subdirs;
|
||||
|
||||
Project(const Project *parent, const std::string &path, bool build);
|
||||
const Project *root() const;
|
||||
bool cmake_minimum_version(int major, int minor) const;
|
||||
};
|
||||
|
||||
bool is_root_path(const std::string &path);
|
||||
|
||||
+4
-16
@@ -2,10 +2,8 @@
|
||||
#include "build.hpp"
|
||||
#include "cmake_generator.hpp"
|
||||
#include "help.hpp"
|
||||
|
||||
#include "fs.hpp"
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -37,17 +35,17 @@ const char *handle_args(int argc, char **argv) {
|
||||
} else if (main_arg == "build") {
|
||||
auto ret = build::run(argc, argv);
|
||||
if (ret)
|
||||
return "CMake build error!";
|
||||
throw std::runtime_error("CMake build failed!");
|
||||
return "CMake build completed!";
|
||||
} else if (main_arg == "install") {
|
||||
auto ret = build::install();
|
||||
if (ret)
|
||||
return "CMake install error!";
|
||||
throw std::runtime_error("CMake install failed!");
|
||||
return "CMake install completed!";
|
||||
} else if (main_arg == "clean") {
|
||||
auto ret = build::clean();
|
||||
if (ret)
|
||||
return "CMake clean error!";
|
||||
throw std::runtime_error("CMake clean failed!");
|
||||
return "Cleaned build directory!";
|
||||
} else {
|
||||
throw std::runtime_error(cmkr::help::message());
|
||||
@@ -55,13 +53,3 @@ const char *handle_args(int argc, char **argv) {
|
||||
}
|
||||
} // namespace args
|
||||
} // namespace cmkr
|
||||
|
||||
const char *cmkr_args_handle_args(int argc, char **argv) {
|
||||
try {
|
||||
return cmkr::args::handle_args(argc, argv);
|
||||
} catch (const std::exception &e) {
|
||||
return e.what();
|
||||
} catch (...) {
|
||||
return "Unknown error!";
|
||||
}
|
||||
}
|
||||
|
||||
+4
-38
@@ -1,14 +1,10 @@
|
||||
#include "build.hpp"
|
||||
#include "cmake_generator.hpp"
|
||||
#include "error.hpp"
|
||||
#include "project_parser.hpp"
|
||||
|
||||
#include "fs.hpp"
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <system_error>
|
||||
|
||||
namespace cmkr {
|
||||
namespace build {
|
||||
@@ -17,7 +13,7 @@ int run(int argc, char **argv) {
|
||||
parser::Project project(nullptr, ".", true);
|
||||
if (argc > 2) {
|
||||
for (int i = 2; i < argc; ++i) {
|
||||
project.build_args.push_back(argv[i]);
|
||||
project.build_args.emplace_back(argv[i]);
|
||||
}
|
||||
}
|
||||
std::stringstream ss;
|
||||
@@ -48,13 +44,13 @@ int run(int argc, char **argv) {
|
||||
}
|
||||
|
||||
int clean() {
|
||||
bool ret = false;
|
||||
bool success = false;
|
||||
parser::Project project(nullptr, ".", true);
|
||||
if (fs::exists(project.build_dir)) {
|
||||
ret = fs::remove_all(project.build_dir);
|
||||
success = fs::remove_all(project.build_dir);
|
||||
fs::create_directory(project.build_dir);
|
||||
}
|
||||
return !ret;
|
||||
return success ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
|
||||
int install() {
|
||||
@@ -64,33 +60,3 @@ int install() {
|
||||
}
|
||||
} // namespace build
|
||||
} // namespace cmkr
|
||||
|
||||
int cmkr_build_run(int argc, char **argv) {
|
||||
try {
|
||||
return cmkr::build::run(argc, argv);
|
||||
} catch (const std::system_error &e) {
|
||||
return e.code().value();
|
||||
} catch (...) {
|
||||
return cmkr::error::Status(cmkr::error::Status::Code::BuildError);
|
||||
}
|
||||
}
|
||||
|
||||
int cmkr_build_clean(void) {
|
||||
try {
|
||||
return cmkr::build::clean();
|
||||
} catch (const std::system_error &e) {
|
||||
return e.code().value();
|
||||
} catch (...) {
|
||||
return cmkr::error::Status(cmkr::error::Status::Code::CleanError);
|
||||
}
|
||||
}
|
||||
|
||||
int cmkr_build_install(void) {
|
||||
try {
|
||||
return cmkr::build::install();
|
||||
} catch (const std::system_error &e) {
|
||||
return e.code().value();
|
||||
} catch (...) {
|
||||
return cmkr::error::Status(cmkr::error::Status::Code::InstallError);
|
||||
}
|
||||
}
|
||||
|
||||
+438
-121
@@ -1,19 +1,48 @@
|
||||
#include "cmake_generator.hpp"
|
||||
#include "error.hpp"
|
||||
#include "literals.hpp"
|
||||
#include <resources/cmkr.hpp>
|
||||
|
||||
#include "fs.hpp"
|
||||
#include "project_parser.hpp"
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <fstream>
|
||||
|
||||
namespace cmkr {
|
||||
namespace gen {
|
||||
|
||||
static std::string format(const char *format, tsl::ordered_map<std::string, std::string> variables) {
|
||||
/*
|
||||
Location: CMake/share/cmake-3.26/Modules
|
||||
rg "set\(CMAKE_(.+)_SOURCE_FILE_EXTENSIONS"
|
||||
|
||||
Links:
|
||||
- https://gitlab.kitware.com/cmake/cmake/-/issues/24340
|
||||
- https://cmake.org/cmake/help/latest/command/enable_language.html
|
||||
*/
|
||||
|
||||
static tsl::ordered_map<std::string, std::vector<std::string>> known_languages = {
|
||||
{"ASM", {".s", ".S", ".asm", ".abs", ".msa", ".s90", ".s43", ".s85", ".s51"}},
|
||||
{"ASM-ATT", {".s", ".asm"}},
|
||||
{"ASM_MARMASM", {".asm"}},
|
||||
{"ASM_MASM", {".asm"}},
|
||||
{"ASM_NASM", {".nasm", ".asm"}},
|
||||
{"C", {".c", ".m"}},
|
||||
{"CSharp", {".cs"}},
|
||||
{"CUDA", {".cu"}},
|
||||
{"CXX", {".C", ".M", ".c++", ".cc", ".cpp", ".cxx", ".m", ".mm", ".mpp", ".CPP", ".ixx", ".cppm"}},
|
||||
{"Fortran", {".f", ".F", ".fpp", ".FPP", ".f77", ".F77", ".f90", ".F90", ".for", ".For", ".FOR", ".f95", ".F95", ".cuf", ".CUF"}},
|
||||
{"HIP", {".hip"}},
|
||||
{"ISPC", {".ispc"}},
|
||||
{"Java", {".java"}},
|
||||
{"OBJC", {".m"}},
|
||||
{"OBJCXX", {".M", ".m", ".mm"}},
|
||||
{"RC", {".rc", ".RC"}},
|
||||
{"Swift", {".swift"}},
|
||||
};
|
||||
|
||||
static std::string format(const char *format, const tsl::ordered_map<std::string, std::string> &variables) {
|
||||
std::string s = format;
|
||||
for (const auto &itr : variables) {
|
||||
size_t start_pos = 0;
|
||||
@@ -25,7 +54,7 @@ static std::string format(const char *format, tsl::ordered_map<std::string, std:
|
||||
return s;
|
||||
}
|
||||
|
||||
static std::vector<std::string> expand_cmake_path(const fs::path &name, const fs::path &toml_dir, bool root_project) {
|
||||
static std::vector<std::string> expand_cmake_path(const fs::path &name, const fs::path &toml_dir, bool is_root_project) {
|
||||
std::vector<std::string> temp;
|
||||
|
||||
auto extract_suffix = [](const fs::path &base, const fs::path &full) {
|
||||
@@ -38,7 +67,7 @@ static std::vector<std::string> expand_cmake_path(const fs::path &name, const fs
|
||||
auto stem = name.filename().stem().string();
|
||||
auto ext = name.extension();
|
||||
|
||||
if (root_project && stem == "**" && name == name.filename()) {
|
||||
if (is_root_project && stem == "**" && name == name.filename()) {
|
||||
throw std::runtime_error("Recursive globbing not allowed in project root: " + name.string());
|
||||
}
|
||||
|
||||
@@ -66,11 +95,11 @@ static std::vector<std::string> expand_cmake_path(const fs::path &name, const fs
|
||||
return temp;
|
||||
}
|
||||
|
||||
static std::vector<std::string> expand_cmake_paths(const std::vector<std::string> &sources, const fs::path &toml_dir, bool root_project) {
|
||||
static std::vector<std::string> expand_cmake_paths(const std::vector<std::string> &sources, const fs::path &toml_dir, bool is_root_project) {
|
||||
// TODO: add duplicate checking
|
||||
std::vector<std::string> result;
|
||||
for (const auto &src : sources) {
|
||||
auto expanded = expand_cmake_path(src, toml_dir, root_project);
|
||||
auto expanded = expand_cmake_path(src, toml_dir, is_root_project);
|
||||
for (const auto &f : expanded) {
|
||||
result.push_back(f);
|
||||
}
|
||||
@@ -89,18 +118,111 @@ static void create_file(const fs::path &path, const std::string &contents) {
|
||||
ofs << contents;
|
||||
}
|
||||
|
||||
static std::string read_file(const fs::path &path) {
|
||||
std::ifstream ifs(path, std::ios::binary);
|
||||
if (!ifs) {
|
||||
throw std::runtime_error("Failed to read " + path.string());
|
||||
}
|
||||
std::string contents;
|
||||
ifs.seekg(0, std::ios::end);
|
||||
contents.resize(ifs.tellg());
|
||||
ifs.seekg(0, std::ios::beg);
|
||||
ifs.read(&contents[0], contents.size());
|
||||
return contents;
|
||||
}
|
||||
|
||||
// CMake target name rules: https://cmake.org/cmake/help/latest/policy/CMP0037.html [A-Za-z0-9_.+\-]
|
||||
// TOML bare keys: non-empty strings composed only of [A-Za-z0-9_-]
|
||||
// We replace all non-TOML bare key characters with _
|
||||
static std::string escape_project_name(const std::string &name) {
|
||||
std::string escaped;
|
||||
escaped.reserve(name.length());
|
||||
for (auto ch : name) {
|
||||
if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '-') {
|
||||
escaped += ch;
|
||||
} else {
|
||||
escaped += '_';
|
||||
}
|
||||
}
|
||||
return escaped;
|
||||
}
|
||||
|
||||
static void generate_gitfile(const char *gitfile, const std::vector<std::string> &desired_lines) {
|
||||
// Reference: https://github.com/github/linguist/blob/master/docs/overrides.md#summary
|
||||
auto lines = desired_lines;
|
||||
auto generate = [&lines](const char *newline) {
|
||||
std::string generated;
|
||||
generated += "# cmkr";
|
||||
generated += newline;
|
||||
for (const auto &line : lines) {
|
||||
generated += line;
|
||||
generated += newline;
|
||||
}
|
||||
return generated;
|
||||
};
|
||||
if (!fs::exists(gitfile)) {
|
||||
create_file(gitfile, generate("\n"));
|
||||
} else {
|
||||
auto contents = read_file(gitfile);
|
||||
std::string line;
|
||||
auto cr = 0, lf = 0;
|
||||
auto flush_line = [&line, &lines]() {
|
||||
auto itr = std::find(lines.begin(), lines.end(), line);
|
||||
if (itr != lines.end()) {
|
||||
lines.erase(itr);
|
||||
}
|
||||
line.clear();
|
||||
};
|
||||
for (size_t i = 0; i < contents.length(); i++) {
|
||||
if (contents[i] == '\r') {
|
||||
cr++;
|
||||
continue;
|
||||
}
|
||||
if (contents[i] == '\n') {
|
||||
lf++;
|
||||
flush_line();
|
||||
} else {
|
||||
line += contents[i];
|
||||
}
|
||||
}
|
||||
if (!line.empty()) {
|
||||
flush_line();
|
||||
}
|
||||
|
||||
if (!lines.empty()) {
|
||||
// Append the cmkr .gitattributes using the detected newline
|
||||
auto newline = cr == lf ? "\r\n" : "\n";
|
||||
if (!contents.empty() && contents.back() != '\n') {
|
||||
contents += newline;
|
||||
}
|
||||
contents += newline;
|
||||
contents += generate(newline);
|
||||
create_file(gitfile, contents);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void generate_project(const std::string &type) {
|
||||
const auto name = fs::current_path().stem().string();
|
||||
const auto name = escape_project_name(fs::current_path().stem().string());
|
||||
if (fs::exists(fs::current_path() / "cmake.toml")) {
|
||||
throw std::runtime_error("Cannot initialize a project when cmake.toml already exists!");
|
||||
}
|
||||
|
||||
// Check if the folder is empty before creating any files
|
||||
auto is_empty = fs::is_empty(fs::current_path());
|
||||
|
||||
// Automatically generate .gitattributes to not skew the statistics of the repo
|
||||
generate_gitfile(".gitattributes", {"/**/CMakeLists.txt linguist-generated", "/**/cmkr.cmake linguist-vendored"});
|
||||
|
||||
// Generate .gitignore with reasonable defaults for CMake
|
||||
generate_gitfile(".gitignore", {"build*/", "cmake-build*/", ".idea/", ".vscode/"});
|
||||
|
||||
tsl::ordered_map<std::string, std::string> variables = {
|
||||
{"@name", name},
|
||||
{"@type", type},
|
||||
};
|
||||
|
||||
if (!fs::is_empty(fs::current_path())) {
|
||||
if (!is_empty) {
|
||||
// Make a backup of an existing CMakeLists.txt if it exists
|
||||
std::error_code ec;
|
||||
fs::rename("CMakeLists.txt", "CMakeLists.txt.bak", ec);
|
||||
@@ -126,13 +248,17 @@ void generate_project(const std::string &type) {
|
||||
|
||||
struct CommandEndl {
|
||||
std::stringstream &ss;
|
||||
CommandEndl(std::stringstream &ss) : ss(ss) {}
|
||||
void endl() { ss << '\n'; }
|
||||
explicit CommandEndl(std::stringstream &ss) : ss(ss) {
|
||||
}
|
||||
void endl() {
|
||||
ss << '\n';
|
||||
}
|
||||
};
|
||||
|
||||
struct RawArg {
|
||||
RawArg() = default;
|
||||
RawArg(std::string arg) : arg(std::move(arg)) {}
|
||||
explicit RawArg(std::string arg) : arg(std::move(arg)) {
|
||||
}
|
||||
|
||||
std::string arg;
|
||||
};
|
||||
@@ -148,7 +274,8 @@ struct Command {
|
||||
std::string post_comment;
|
||||
|
||||
Command(std::stringstream &ss, int depth, std::string command, std::string post_comment)
|
||||
: ss(ss), depth(depth), command(std::move(command)), post_comment(std::move(post_comment)) {}
|
||||
: ss(ss), depth(depth), command(std::move(command)), post_comment(std::move(post_comment)) {
|
||||
}
|
||||
|
||||
~Command() noexcept(false) {
|
||||
if (!generated) {
|
||||
@@ -165,7 +292,7 @@ struct Command {
|
||||
// https://cmake.org/cmake/help/latest/manual/cmake-language.7.html#unquoted-argument
|
||||
// NOTE: Normally '/' does not require quoting according to the documentation but this has been the case here
|
||||
// previously, so for backwards compatibility its still here.
|
||||
if (str.find_first_of("()#\"\\'> |/;") == str.npos)
|
||||
if (str.find_first_of("()#\"\\'> |/;") == std::string::npos)
|
||||
return str;
|
||||
std::string result;
|
||||
result += "\"";
|
||||
@@ -314,13 +441,15 @@ static std::string tolf(const std::string &str) {
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
struct Generator {
|
||||
Generator(const parser::Project &project) : project(project) {}
|
||||
Generator(const parser::Project &project, fs::path path) : project(project), path(std::move(path)) {
|
||||
}
|
||||
Generator(const Generator &) = delete;
|
||||
|
||||
const parser::Project &project;
|
||||
fs::path path;
|
||||
std::stringstream ss;
|
||||
int indent = 0;
|
||||
|
||||
@@ -343,13 +472,15 @@ struct Generator {
|
||||
return CommandEndl(ss);
|
||||
}
|
||||
|
||||
void endl() { ss << '\n'; }
|
||||
void endl() {
|
||||
ss << '\n';
|
||||
}
|
||||
|
||||
void inject_includes(const std::vector<std::string> &includes) {
|
||||
if (!includes.empty()) {
|
||||
for (const auto &file : includes) {
|
||||
if (!fs::is_regular_file(file)) {
|
||||
throw std::runtime_error("Include '" + file + "' does not exist");
|
||||
if (!fs::exists(path / file)) {
|
||||
throw std::runtime_error("Include not found: " + file);
|
||||
}
|
||||
cmd("include")(file);
|
||||
}
|
||||
@@ -392,9 +523,10 @@ struct Generator {
|
||||
}
|
||||
|
||||
if (!condition.empty()) {
|
||||
cmd("endif")();
|
||||
cmd("endif")().endl();
|
||||
} else if (!itr.second.empty()) {
|
||||
endl();
|
||||
}
|
||||
endl();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -481,10 +613,21 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
}
|
||||
|
||||
// Root project doesn't have a parent
|
||||
auto root_project = parent_project == nullptr;
|
||||
auto is_root_project = parent_project == nullptr;
|
||||
|
||||
parser::Project project(parent_project, path, false);
|
||||
Generator gen(project);
|
||||
|
||||
for (auto const &lang : project.project_languages) {
|
||||
if (known_languages.find(lang) == known_languages.end()) {
|
||||
if (project.project_allow_unknown_languages) {
|
||||
printf("[warning] Unknown language '%s' specified\n", lang.c_str());
|
||||
} else {
|
||||
throw std::runtime_error("Unknown language '" + lang + "' specified");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Generator gen(project, path);
|
||||
|
||||
// Helper lambdas for more convenient CMake generation
|
||||
auto &ss = gen.ss;
|
||||
@@ -497,9 +640,27 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
comment("See " + cmkr_url + " for more information");
|
||||
endl();
|
||||
|
||||
if (root_project) {
|
||||
if (is_root_project) {
|
||||
cmd("cmake_minimum_required")("VERSION", project.cmake_version).endl();
|
||||
|
||||
if (project.project_msvc_runtime != parser::msvc_last) {
|
||||
comment("Enable support for MSVC_RUNTIME_LIBRARY");
|
||||
cmd("cmake_policy")("SET", "CMP0091", "NEW");
|
||||
|
||||
switch (project.project_msvc_runtime) {
|
||||
case parser::msvc_dynamic:
|
||||
cmd("set")("CMAKE_MSVC_RUNTIME_LIBRARY", "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL");
|
||||
break;
|
||||
case parser::msvc_static:
|
||||
cmd("set")("CMAKE_MSVC_RUNTIME_LIBRARY", "MultiThreaded$<$<CONFIG:Debug>:Debug>");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
endl();
|
||||
}
|
||||
|
||||
// clang-format on
|
||||
if (!project.allow_in_tree) {
|
||||
// clang-format off
|
||||
cmd("if")("CMAKE_SOURCE_DIR", "STREQUAL", "CMAKE_BINARY_DIR");
|
||||
@@ -508,14 +669,13 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
comment("Regenerate CMakeLists.txt automatically in the root project");
|
||||
cmd("set")("CMKR_ROOT_PROJECT", "OFF");
|
||||
// clang-format off
|
||||
cmd("if")("CMAKE_CURRENT_SOURCE_DIR", "STREQUAL", "CMAKE_SOURCE_DIR");
|
||||
cmd("set")("CMKR_ROOT_PROJECT", "ON").endl();
|
||||
|
||||
if (!project.cmkr_include.empty()) {
|
||||
comment("Bootstrap cmkr");
|
||||
comment("Bootstrap cmkr and automatically regenerate CMakeLists.txt");
|
||||
cmd("include")(project.cmkr_include, "OPTIONAL", "RESULT_VARIABLE", "CMKR_INCLUDE_RESULT");
|
||||
cmd("if")("CMKR_INCLUDE_RESULT");
|
||||
cmd("cmkr")();
|
||||
@@ -523,7 +683,10 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
}
|
||||
|
||||
comment("Enable folder support");
|
||||
cmd("set_property")("GLOBAL", "PROPERTY", "USE_FOLDERS", "ON");
|
||||
cmd("set_property")("GLOBAL", "PROPERTY", "USE_FOLDERS", "ON").endl();
|
||||
|
||||
comment("Create a configure-time dependency on cmake.toml to improve IDE support");
|
||||
cmd("configure_file")("cmake.toml", "cmake.toml", "COPYONLY");
|
||||
cmd("endif")().endl();
|
||||
// clang-format on
|
||||
|
||||
@@ -531,15 +694,15 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
if (!project.cmkr_include.empty() && !fs::exists(cmkr_include) && cmkr_include.is_relative()) {
|
||||
create_file(cmkr_include, resources::cmkr);
|
||||
}
|
||||
} else {
|
||||
// clang-format off
|
||||
comment("Create a configure-time dependency on cmake.toml to improve IDE support");
|
||||
cmd("if")("CMKR_ROOT_PROJECT");
|
||||
cmd("configure_file")("cmake.toml", "cmake.toml", "COPYONLY");
|
||||
cmd("endif")().endl();
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
comment("Create a configure-time dependency on cmake.toml to improve IDE support");
|
||||
cmd("if")("CMKR_ROOT_PROJECT");
|
||||
cmd("configure_file")("cmake.toml", "cmake.toml", "COPYONLY");
|
||||
cmd("endif")().endl();
|
||||
// clang-format on
|
||||
|
||||
// TODO: remove support and replace with global compile-features
|
||||
if (!project.cppflags.empty()) {
|
||||
ss << "set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} \"";
|
||||
@@ -570,25 +733,31 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
if (!project.options.empty()) {
|
||||
comment("Options");
|
||||
for (const auto &opt : project.options) {
|
||||
cmd("option")(opt.name, RawArg(Command::quote(opt.comment)), opt.val ? "ON" : "OFF");
|
||||
std::string default_val;
|
||||
if (opt.value.index() == 0) {
|
||||
default_val = mpark::get<0>(opt.value) ? "ON" : "OFF";
|
||||
} else {
|
||||
default_val = mpark::get<1>(opt.value);
|
||||
}
|
||||
cmd("option")(opt.name, RawArg(Command::quote(opt.help)), default_val);
|
||||
}
|
||||
endl();
|
||||
}
|
||||
|
||||
if (!project.settings.empty()) {
|
||||
comment("Settings");
|
||||
for (const auto &set : project.settings) {
|
||||
if (!project.variables.empty()) {
|
||||
comment("Variables");
|
||||
for (const auto &set : project.variables) {
|
||||
std::string set_val;
|
||||
if (set.val.index() == 1) {
|
||||
set_val = mpark::get<1>(set.val);
|
||||
if (set.value.index() == 1) {
|
||||
set_val = mpark::get<1>(set.value);
|
||||
} else {
|
||||
set_val = mpark::get<0>(set.val) ? "ON" : "OFF";
|
||||
set_val = mpark::get<0>(set.value) ? "ON" : "OFF";
|
||||
}
|
||||
|
||||
if (set.cache) {
|
||||
auto typ = set.val.index() == 1 ? "STRING" : "BOOL";
|
||||
auto typ = set.value.index() == 1 ? "STRING" : "BOOL";
|
||||
auto force = set.force ? "FORCE" : "";
|
||||
cmd("set")(set.name, set_val, typ, set.comment, force);
|
||||
cmd("set")(set.name, set_val, typ, set.help, force);
|
||||
} else {
|
||||
cmd("set")(set.name, set_val);
|
||||
}
|
||||
@@ -604,12 +773,23 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
auto version = std::make_pair("VERSION", project.project_version);
|
||||
auto description = std::make_pair("DESCRIPTION", project.project_description);
|
||||
cmd("project")(project.project_name, languages, version, description).endl();
|
||||
|
||||
for (const auto &language : project.project_languages) {
|
||||
if (language == "CSharp") {
|
||||
cmd("include")("CSharpUtilities").endl();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gen.conditional_includes(project.include_after);
|
||||
gen.conditional_cmake(project.cmake_after);
|
||||
|
||||
if (!project.vcpkg.packages.empty()) {
|
||||
if (project.vcpkg.enabled()) {
|
||||
if (!is_root_project) {
|
||||
throw std::runtime_error("[vcpkg] is only supported in the root project");
|
||||
}
|
||||
|
||||
// Allow the user to specify a url or derive it from the version
|
||||
auto url = project.vcpkg.url;
|
||||
auto version_name = url;
|
||||
@@ -622,7 +802,8 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
}
|
||||
|
||||
// Show a nicer error than vcpkg when specifying an invalid package name
|
||||
for (const auto &package : project.vcpkg.packages) {
|
||||
const auto &packages = project.vcpkg.packages;
|
||||
for (const auto &package : packages) {
|
||||
if (!vcpkg_valid_identifier(package.name)) {
|
||||
throw std::runtime_error("Invalid [vcpkg].packages name '" + package.name + "' (needs to be lowercase alphanumeric)");
|
||||
}
|
||||
@@ -632,14 +813,20 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
// clang-format off
|
||||
cmd("if")("CMKR_ROOT_PROJECT", "AND", "NOT", "CMKR_DISABLE_VCPKG");
|
||||
cmd("include")("FetchContent");
|
||||
cmd("message")("STATUS", "Fetching vcpkg (" + version_name + ")...");
|
||||
cmd("FetchContent_Declare")("vcpkg", "URL", url);
|
||||
// Not using FetchContent_MakeAvailable here in case vcpkg adds CMakeLists.txt
|
||||
cmd("FetchContent_GetProperties")("vcpkg");
|
||||
cmd("if")("NOT", "vcpkg_POPULATED");
|
||||
cmd("FetchContent_Populate")("vcpkg");
|
||||
cmd("include")("${vcpkg_SOURCE_DIR}/scripts/buildsystems/vcpkg.cmake");
|
||||
comment("Fix warnings about DOWNLOAD_EXTRACT_TIMESTAMP");
|
||||
// clang-format off
|
||||
cmd("if")("POLICY", "CMP0135");
|
||||
cmd("cmake_policy")("SET", "CMP0135", "NEW");
|
||||
cmd("endif")();
|
||||
// clang-format on
|
||||
cmd("message")("STATUS", "Fetching vcpkg (" + version_name + ")...");
|
||||
cmd("FetchContent_Declare")("vcpkg", "URL", url);
|
||||
// Not using FetchContent_MakeAvailable here in case vcpkg adds CMakeLists.txt
|
||||
cmd("FetchContent_GetProperties")("vcpkg");
|
||||
cmd("if")("NOT", "vcpkg_POPULATED");
|
||||
cmd("FetchContent_Populate")("vcpkg");
|
||||
cmd("include")("${vcpkg_SOURCE_DIR}/scripts/buildsystems/vcpkg.cmake");
|
||||
cmd("endif")();
|
||||
cmd("endif")();
|
||||
endl();
|
||||
// clang-format on
|
||||
@@ -656,7 +843,6 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
"dependencies": [
|
||||
)";
|
||||
|
||||
const auto &packages = project.vcpkg.packages;
|
||||
for (size_t i = 0; i < packages.size(); i++) {
|
||||
const auto &package = packages[i];
|
||||
const auto &features = package.features;
|
||||
@@ -710,13 +896,21 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
|
||||
if (!project.contents.empty()) {
|
||||
cmd("include")("FetchContent").endl();
|
||||
if (!project.root()->vcpkg.enabled()) {
|
||||
comment("Fix warnings about DOWNLOAD_EXTRACT_TIMESTAMP");
|
||||
// clang-format off
|
||||
cmd("if")("POLICY", "CMP0135");
|
||||
cmd("cmake_policy")("SET", "CMP0135", "NEW");
|
||||
cmd("endif")();
|
||||
// clang-format on
|
||||
}
|
||||
for (const auto &content : project.contents) {
|
||||
ConditionScope cs(gen, content.condition);
|
||||
|
||||
gen.conditional_includes(content.include_before);
|
||||
gen.conditional_cmake(content.cmake_before);
|
||||
|
||||
std::string version_info = "";
|
||||
std::string version_info;
|
||||
if (content.arguments.contains("GIT_TAG")) {
|
||||
version_info = " (" + content.arguments.at("GIT_TAG") + ")";
|
||||
} else if (content.arguments.contains("SVN_REVISION")) {
|
||||
@@ -747,7 +941,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
|
||||
auto add_subdir = [&](const std::string &dir) {
|
||||
// clang-format off
|
||||
comment(dir);
|
||||
comment("Subdirectory: " + dir);
|
||||
cmd("set")("CMKR_CMAKE_FOLDER", "${CMAKE_FOLDER}");
|
||||
cmd("if")("CMAKE_FOLDER");
|
||||
cmd("set")("CMAKE_FOLDER", "${CMAKE_FOLDER}/" + dir);
|
||||
@@ -757,17 +951,19 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
// clang-format on
|
||||
|
||||
cmd("add_subdirectory")(dir);
|
||||
cmd("set")("CMAKE_FOLDER", "${CMKR_CMAKE_FOLDER}").endl();
|
||||
cmd("set")("CMAKE_FOLDER", "${CMKR_CMAKE_FOLDER}");
|
||||
};
|
||||
|
||||
// generate_cmake is called on the subdirectories recursively later
|
||||
if (!project.project_subdirs.empty()) {
|
||||
gen.handle_condition(project.project_subdirs, [&](const std::string &, const std::vector<std::string> &subdirs) {
|
||||
for (const auto &dir : subdirs) {
|
||||
add_subdir(dir);
|
||||
for (size_t i = 0; i < subdirs.size(); i++) {
|
||||
add_subdir(subdirs[i]);
|
||||
if (i + 1 < subdirs.size()) {
|
||||
endl();
|
||||
}
|
||||
}
|
||||
});
|
||||
endl();
|
||||
}
|
||||
for (const auto &subdir : project.subdirs) {
|
||||
ConditionScope cs(gen, subdir.condition);
|
||||
@@ -776,22 +972,48 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
gen.conditional_cmake(subdir.cmake_before);
|
||||
|
||||
add_subdir(subdir.name);
|
||||
endl();
|
||||
|
||||
gen.conditional_includes(subdir.include_after);
|
||||
gen.conditional_cmake(subdir.cmake_after);
|
||||
}
|
||||
|
||||
if (!project.targets.empty()) {
|
||||
for (size_t i = 0; i < project.targets.size(); i++) {
|
||||
if (i > 0) {
|
||||
endl();
|
||||
}
|
||||
// The implicit default is ["C", "CXX"], so make sure this list isn't
|
||||
// empty or projects without languages explicitly defined will error.
|
||||
auto project_languages = project.project_languages;
|
||||
if (project_languages.empty())
|
||||
project_languages = {"C", "CXX"};
|
||||
|
||||
// All acceptable extensions based off our given languages.
|
||||
tsl::ordered_set<std::string> project_extensions;
|
||||
for (const auto &language : project_languages) {
|
||||
auto itr = known_languages.find(language);
|
||||
if (itr != known_languages.end()) {
|
||||
project_extensions.insert(itr->second.begin(), itr->second.end());
|
||||
}
|
||||
}
|
||||
|
||||
auto contains_language_source = [&project_extensions](const std::vector<std::string> &sources) {
|
||||
for (const auto &source : sources) {
|
||||
auto extension = fs::path(source).extension().string();
|
||||
if (project_extensions.count(extension) > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if (!project.targets.empty()) {
|
||||
auto project_root = project.root();
|
||||
for (size_t i = 0; i < project.targets.size(); i++) {
|
||||
const auto &target = project.targets[i];
|
||||
|
||||
auto throw_target_error = [&target](const std::string &message) { throw std::runtime_error("[target." + target.name + "] " + message); };
|
||||
|
||||
const parser::Template *tmplate = nullptr;
|
||||
std::unique_ptr<ConditionScope> tmplate_cs{};
|
||||
|
||||
comment("Target " + target.name);
|
||||
comment("Target: " + target.name);
|
||||
|
||||
// Check if this target is using a template.
|
||||
if (target.type == parser::target_template) {
|
||||
@@ -805,56 +1027,149 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
|
||||
ConditionScope cs(gen, target.condition);
|
||||
|
||||
cmd("set")("CMKR_TARGET", target.name);
|
||||
// Detect if there is cmake included before/after the target
|
||||
auto has_include_before = false;
|
||||
auto has_include_after = false;
|
||||
{
|
||||
auto has_include = [](const parser::ConditionVector &includes) {
|
||||
for (const auto &itr : includes) {
|
||||
for (const auto &jtr : itr.second) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
auto has_include_helper = [&](const parser::Target &target) {
|
||||
if (!target.cmake_before.empty() || has_include(target.include_before)) {
|
||||
has_include_before = true;
|
||||
}
|
||||
if (!target.cmake_after.empty() || has_include(target.include_after)) {
|
||||
has_include_after = true;
|
||||
}
|
||||
};
|
||||
if (tmplate != nullptr) {
|
||||
has_include_helper(tmplate->outline);
|
||||
}
|
||||
has_include_helper(target);
|
||||
}
|
||||
|
||||
// Generate the include before
|
||||
if (has_include_before) {
|
||||
cmd("set")("CMKR_TARGET", target.name);
|
||||
}
|
||||
if (tmplate != nullptr) {
|
||||
gen.conditional_includes(tmplate->outline.include_before);
|
||||
gen.conditional_cmake(tmplate->outline.cmake_before);
|
||||
}
|
||||
|
||||
gen.conditional_includes(target.include_before);
|
||||
gen.conditional_cmake(target.cmake_before);
|
||||
|
||||
auto sources_var = target.name + "_SOURCES";
|
||||
// Merge the sources from the template and the target. The sources
|
||||
// without condition need to be processed first
|
||||
parser::Condition<tsl::ordered_set<std::string>> msources;
|
||||
msources[""].clear();
|
||||
|
||||
bool added_toml = false;
|
||||
cmd("set")(sources_var, RawArg("\"\"")).endl();
|
||||
|
||||
if (tmplate != nullptr) {
|
||||
gen.handle_condition(tmplate->outline.sources, [&](const std::string &condition, const std::vector<std::string> &condition_sources) {
|
||||
auto sources = expand_cmake_paths(condition_sources, path, root_project);
|
||||
if (sources.empty()) {
|
||||
auto source_key = condition.empty() ? "sources" : (condition + ".sources");
|
||||
throw std::runtime_error(target.name + " " + source_key + " wildcard found 0 files");
|
||||
auto merge_sources = [&msources](const parser::ConditionVector &sources) {
|
||||
for (const auto &itr : sources) {
|
||||
auto &source_list = msources[itr.first];
|
||||
for (const auto &source : itr.second) {
|
||||
source_list.insert(source);
|
||||
}
|
||||
cmd("list")("APPEND", sources_var, sources);
|
||||
});
|
||||
}
|
||||
};
|
||||
if (tmplate != nullptr) {
|
||||
merge_sources(tmplate->outline.sources);
|
||||
}
|
||||
merge_sources(target.sources);
|
||||
|
||||
// Improve IDE support
|
||||
if (target.type != parser::target_interface) {
|
||||
msources[""].insert("cmake.toml");
|
||||
}
|
||||
|
||||
gen.handle_condition(target.sources, [&](const std::string &condition, const std::vector<std::string> &condition_sources) {
|
||||
auto sources = expand_cmake_paths(condition_sources, path, root_project);
|
||||
// If there are only conditional sources we generate a 'set' to
|
||||
// create an empty source list. The rest is then appended using
|
||||
// 'list(APPEND ...)'
|
||||
auto has_sources = false;
|
||||
for (const auto &itr : msources) {
|
||||
if (!itr.second.empty()) {
|
||||
has_sources = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
auto sources_var = target.name + "_SOURCES";
|
||||
auto sources_with_set = true;
|
||||
if (has_sources && msources[""].empty()) {
|
||||
sources_with_set = false;
|
||||
cmd("set")(sources_var, RawArg("\"\"")).endl();
|
||||
}
|
||||
|
||||
gen.handle_condition(msources, [&](const std::string &condition, const tsl::ordered_set<std::string> &source_set) {
|
||||
std::vector<std::string> condition_sources;
|
||||
condition_sources.reserve(source_set.size());
|
||||
for (const auto &source : source_set) {
|
||||
condition_sources.push_back(source);
|
||||
}
|
||||
auto sources = expand_cmake_paths(condition_sources, path, is_root_project);
|
||||
if (sources.empty()) {
|
||||
auto source_key = condition.empty() ? "sources" : (condition + ".sources");
|
||||
throw std::runtime_error(target.name + " " + source_key + " wildcard found 0 files");
|
||||
throw_target_error(source_key + " wildcard found 0 files");
|
||||
}
|
||||
// Do not add cmake.toml twice
|
||||
if (!added_toml && std::find(sources.begin(), sources.end(), "cmake.toml") != sources.end()) {
|
||||
added_toml = true;
|
||||
|
||||
// Make sure there are source files for the languages used by the project
|
||||
switch (target.type) {
|
||||
case parser::target_executable:
|
||||
case parser::target_library:
|
||||
case parser::target_shared:
|
||||
case parser::target_static:
|
||||
case parser::target_object:
|
||||
if (!contains_language_source(sources)) {
|
||||
std::string extensions;
|
||||
for (const auto &language : project_extensions) {
|
||||
if (!extensions.empty()) {
|
||||
extensions += " ";
|
||||
}
|
||||
extensions += language;
|
||||
}
|
||||
throw_target_error("No sources found with valid extensions (" + extensions + ")");
|
||||
}
|
||||
|
||||
// Make sure relative source files exist
|
||||
for (const auto &source : sources) {
|
||||
auto var_index = source.find("${");
|
||||
if (var_index != std::string::npos)
|
||||
continue;
|
||||
const auto &source_path = fs::path(path) / source;
|
||||
if (!fs::exists(source_path)) {
|
||||
throw_target_error("Source file not found: " + source);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (sources_with_set) {
|
||||
// This is a sanity check to make sure the unconditional sources are first
|
||||
if (!condition.empty()) {
|
||||
throw_target_error("Unreachable code, make sure unconditional sources are first");
|
||||
}
|
||||
cmd("set")(sources_var, sources);
|
||||
sources_with_set = false;
|
||||
} else {
|
||||
cmd("list")("APPEND", sources_var, sources);
|
||||
}
|
||||
cmd("list")("APPEND", sources_var, sources);
|
||||
});
|
||||
|
||||
auto target_type = target.type;
|
||||
|
||||
if (tmplate != nullptr) {
|
||||
if (target_type != parser::target_template) {
|
||||
throw_target_error("Unreachable code, unexpected target type for template");
|
||||
}
|
||||
target_type = tmplate->outline.type;
|
||||
}
|
||||
|
||||
if (!added_toml && target_type != parser::target_interface) {
|
||||
cmd("list")("APPEND", sources_var, std::vector<std::string>{"cmake.toml"}).endl();
|
||||
}
|
||||
cmd("set")("CMKR_SOURCES", "${" + sources_var + "}");
|
||||
|
||||
std::string add_command;
|
||||
std::string target_type_string;
|
||||
std::string target_scope;
|
||||
@@ -898,7 +1213,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
target_scope = "PUBLIC";
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("Unimplemented enum value");
|
||||
throw_target_error("Unimplemented enum value");
|
||||
}
|
||||
|
||||
// Handle custom add commands from templates.
|
||||
@@ -910,34 +1225,20 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
cmd(add_command)(target.name, target_type_string, "${" + sources_var + "}");
|
||||
} else {
|
||||
cmd(add_command)(target.name, target_type_string).endl();
|
||||
|
||||
// clang-format off
|
||||
cmd("if")(sources_var);
|
||||
cmd("target_sources")(target.name, target_type == parser::target_interface ? "INTERFACE" : "PRIVATE", "${" + sources_var + "}");
|
||||
cmd("endif")().endl();
|
||||
// clang-format on
|
||||
if (has_sources) {
|
||||
cmd("target_sources")(target.name, target_type == parser::target_interface ? "INTERFACE" : "PRIVATE",
|
||||
"${" + sources_var + "}");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cmd(add_command)(target.name, target_type_string).endl();
|
||||
|
||||
// clang-format off
|
||||
cmd("if")(sources_var);
|
||||
if (has_sources) {
|
||||
cmd("target_sources")(target.name, target_type == parser::target_interface ? "INTERFACE" : "PRIVATE", "${" + sources_var + "}");
|
||||
cmd("endif")().endl();
|
||||
// clang-format on
|
||||
}
|
||||
}
|
||||
|
||||
// The first executable target will become the Visual Studio startup project
|
||||
if (target_type == parser::target_executable) {
|
||||
cmd("get_directory_property")("CMKR_VS_STARTUP_PROJECT", "DIRECTORY", "${PROJECT_SOURCE_DIR}", "DEFINITION", "VS_STARTUP_PROJECT");
|
||||
// clang-format off
|
||||
cmd("if")("NOT", "CMKR_VS_STARTUP_PROJECT");
|
||||
cmd("set_property")("DIRECTORY", "${PROJECT_SOURCE_DIR}", "PROPERTY", "VS_STARTUP_PROJECT", target.name);
|
||||
cmd("endif")().endl();
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
if (!target.sources.empty()) {
|
||||
// TODO: support sources from other directories
|
||||
if (has_sources) {
|
||||
cmd("source_group")("TREE", "${CMAKE_CURRENT_SOURCE_DIR}", "FILES", "${" + sources_var + "}").endl();
|
||||
}
|
||||
|
||||
@@ -990,23 +1291,39 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
}
|
||||
|
||||
gen.handle_condition(props, [&](const std::string &, const tsl::ordered_map<std::string, std::string> &properties) {
|
||||
for (const auto &propItr : properties) {
|
||||
if (propItr.first == "MSVC_RUNTIME_LIBRARY") {
|
||||
if (project_root->project_msvc_runtime == parser::msvc_last) {
|
||||
throw_target_error("You cannot set msvc-runtime without setting the root [project].msvc-runtime");
|
||||
}
|
||||
}
|
||||
}
|
||||
cmd("set_target_properties")(target.name, "PROPERTIES", properties);
|
||||
});
|
||||
}
|
||||
|
||||
// The first executable target will become the Visual Studio startup project
|
||||
// TODO: this is not working properly
|
||||
if (target_type == parser::target_executable) {
|
||||
cmd("get_directory_property")("CMKR_VS_STARTUP_PROJECT", "DIRECTORY", "${PROJECT_SOURCE_DIR}", "DEFINITION", "VS_STARTUP_PROJECT");
|
||||
// clang-format off
|
||||
cmd("if")("NOT", "CMKR_VS_STARTUP_PROJECT");
|
||||
cmd("set_property")("DIRECTORY", "${PROJECT_SOURCE_DIR}", "PROPERTY", "VS_STARTUP_PROJECT", target.name);
|
||||
cmd("endif")().endl();
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
// Generate the include after
|
||||
if (!has_include_before && has_include_after) {
|
||||
cmd("set")("CMKR_TARGET", target.name);
|
||||
}
|
||||
gen.conditional_includes(target.include_after);
|
||||
gen.conditional_cmake(target.cmake_after);
|
||||
|
||||
if (tmplate != nullptr) {
|
||||
gen.conditional_includes(tmplate->outline.include_after);
|
||||
gen.conditional_cmake(tmplate->outline.cmake_after);
|
||||
}
|
||||
|
||||
cmd("unset")("CMKR_TARGET");
|
||||
cmd("unset")("CMKR_SOURCES");
|
||||
}
|
||||
|
||||
endl();
|
||||
}
|
||||
|
||||
if (!project.tests.empty()) {
|
||||
@@ -1032,7 +1349,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
auto dirs = std::make_pair("DIRS", inst.dirs);
|
||||
std::vector<std::string> files_data;
|
||||
if (!inst.files.empty()) {
|
||||
files_data = expand_cmake_paths(inst.files, path, root_project);
|
||||
files_data = expand_cmake_paths(inst.files, path, is_root_project);
|
||||
if (files_data.empty()) {
|
||||
throw std::runtime_error("[[install]] files wildcard did not resolve to any files");
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
#include "error.hpp"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace cmkr {
|
||||
namespace error {
|
||||
|
||||
Status::Status(Code ec) noexcept : ec_(ec) {}
|
||||
|
||||
Status::operator int() const noexcept { return static_cast<int>(ec_); }
|
||||
|
||||
Status::Code Status::code() const noexcept { return ec_; }
|
||||
|
||||
} // namespace error
|
||||
} // namespace cmkr
|
||||
|
||||
// strings for cmkr::error::Status::Code
|
||||
static const char *err_string[] = {
|
||||
"Success", "Runtime error", "Initialization error", "CMake generation error", "Build error", "Clean error", "Install error",
|
||||
};
|
||||
|
||||
const char *cmkr_error_status(int i) {
|
||||
assert(i >= 0 && i < (sizeof(err_string) / sizeof(*(err_string))));
|
||||
return err_string[i];
|
||||
}
|
||||
+3
-5
@@ -4,7 +4,9 @@
|
||||
namespace cmkr {
|
||||
namespace help {
|
||||
|
||||
const char *version() noexcept { return "cmkr version " CMKR_VERSION; }
|
||||
const char *version() noexcept {
|
||||
return "cmkr version " CMKR_VERSION;
|
||||
}
|
||||
|
||||
const char *message() noexcept {
|
||||
return R"lit(
|
||||
@@ -21,7 +23,3 @@ arguments:
|
||||
}
|
||||
} // namespace help
|
||||
} // namespace cmkr
|
||||
|
||||
const char *cmkr_help_version(void) { return cmkr::help::version(); }
|
||||
|
||||
const char *cmkr_help_message(void) { return cmkr::help::message(); }
|
||||
+178
-44
@@ -4,8 +4,6 @@
|
||||
#include <deque>
|
||||
#include <stdexcept>
|
||||
#include <toml.hpp>
|
||||
#include <tsl/ordered_map.h>
|
||||
#include <tsl/ordered_set.h>
|
||||
|
||||
namespace cmkr {
|
||||
namespace parser {
|
||||
@@ -21,16 +19,27 @@ static TargetType parse_targetType(const std::string &name) {
|
||||
return target_last;
|
||||
}
|
||||
|
||||
const char *msvcRuntimeTypeNames[msvc_last] = {"dynamic", "static"};
|
||||
|
||||
static MsvcRuntimeType parse_msvcRuntimeType(const std::string &name) {
|
||||
for (int i = 0; i < msvc_last; i++) {
|
||||
if (name == msvcRuntimeTypeNames[i]) {
|
||||
return static_cast<MsvcRuntimeType>(i);
|
||||
}
|
||||
}
|
||||
return msvc_last;
|
||||
}
|
||||
|
||||
using TomlBasicValue = toml::basic_value<toml::discard_comments, tsl::ordered_map, std::vector>;
|
||||
|
||||
static std::string format_key_error(const std::string &error, const toml::key &ky, const TomlBasicValue &value) {
|
||||
static std::string format_key_message(const std::string &message, const toml::key &ky, const TomlBasicValue &value) {
|
||||
auto loc = value.location();
|
||||
auto line_number_str = std::to_string(loc.line());
|
||||
auto line_width = line_number_str.length();
|
||||
auto line_str = loc.line_str();
|
||||
const auto &line_str = loc.line_str();
|
||||
|
||||
std::ostringstream oss;
|
||||
oss << "[error] " << error << '\n';
|
||||
oss << message << "\n";
|
||||
oss << " --> " << loc.file_name() << ':' << loc.line() << '\n';
|
||||
|
||||
oss << std::string(line_width + 2, ' ') << "|\n";
|
||||
@@ -48,14 +57,24 @@ static std::string format_key_error(const std::string &error, const toml::key &k
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
static void throw_key_error(const std::string &error, const toml::key &ky, const TomlBasicValue &value) {
|
||||
throw std::runtime_error(format_key_message("[error] " + error, ky, value));
|
||||
}
|
||||
|
||||
static void print_key_warning(const std::string &message, const toml::key &ky, const TomlBasicValue &value) {
|
||||
puts(format_key_message("[warning] " + message, ky, value).c_str());
|
||||
}
|
||||
|
||||
class TomlChecker {
|
||||
const TomlBasicValue &m_v;
|
||||
tsl::ordered_set<toml::key> m_visited;
|
||||
tsl::ordered_set<toml::key> m_conditionVisited;
|
||||
|
||||
public:
|
||||
TomlChecker(const TomlBasicValue &v, const toml::key &ky) : m_v(toml::find(v, ky)) {}
|
||||
TomlChecker(const TomlBasicValue &v) : m_v(v) {}
|
||||
TomlChecker(const TomlBasicValue &v, const toml::key &ky) : m_v(toml::find(v, ky)) {
|
||||
}
|
||||
explicit TomlChecker(const TomlBasicValue &v) : m_v(v) {
|
||||
}
|
||||
TomlChecker(const TomlChecker &) = delete;
|
||||
TomlChecker(TomlChecker &&) = delete;
|
||||
|
||||
@@ -109,36 +128,40 @@ class TomlChecker {
|
||||
return toml::find(m_v, ky);
|
||||
}
|
||||
|
||||
void visit(const toml::key &ky) { m_visited.emplace(ky); }
|
||||
void visit(const toml::key &ky) {
|
||||
m_visited.emplace(ky);
|
||||
}
|
||||
|
||||
bool visisted(const toml::key &ky) const { return m_visited.contains(ky); }
|
||||
bool visisted(const toml::key &ky) const {
|
||||
return m_visited.contains(ky);
|
||||
}
|
||||
|
||||
void check(const tsl::ordered_map<std::string, std::string> &conditions) const {
|
||||
for (const auto &itr : m_v.as_table()) {
|
||||
const auto &ky = itr.first;
|
||||
if (m_conditionVisited.contains(ky)) {
|
||||
if (!conditions.contains(ky)) {
|
||||
throw std::runtime_error(format_key_error("Unknown condition '" + ky + "'", ky, itr.second));
|
||||
throw_key_error("Unknown condition '" + ky + "'", ky, itr.second);
|
||||
}
|
||||
|
||||
for (const auto &jtr : itr.second.as_table()) {
|
||||
if (!m_visited.contains(jtr.first)) {
|
||||
throw std::runtime_error(format_key_error("Unknown key '" + jtr.first + "'", jtr.first, jtr.second));
|
||||
throw_key_error("Unknown key '" + jtr.first + "'", jtr.first, jtr.second);
|
||||
}
|
||||
}
|
||||
} else if (!m_visited.contains(ky)) {
|
||||
if (itr.second.is_table()) {
|
||||
for (const auto &jtr : itr.second.as_table()) {
|
||||
if (!m_visited.contains(jtr.first)) {
|
||||
throw std::runtime_error(format_key_error("Unknown key '" + jtr.first + "'", jtr.first, jtr.second));
|
||||
throw_key_error("Unknown key '" + jtr.first + "'", jtr.first, jtr.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw std::runtime_error(format_key_error("Unknown key '" + ky + "'", ky, itr.second));
|
||||
throw_key_error("Unknown key '" + ky + "'", ky, itr.second);
|
||||
} else if (ky == "condition") {
|
||||
std::string condition = itr.second.as_string();
|
||||
if (!conditions.contains(condition)) {
|
||||
throw std::runtime_error(format_key_error("Unknown condition '" + condition + "'", condition, itr.second));
|
||||
throw_key_error("Unknown condition '" + condition + "'", condition, itr.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -152,7 +175,8 @@ class TomlCheckerRoot {
|
||||
bool m_checked = false;
|
||||
|
||||
public:
|
||||
TomlCheckerRoot(const TomlBasicValue &root) : m_root(root) {}
|
||||
explicit TomlCheckerRoot(const TomlBasicValue &root) : m_root(root) {
|
||||
}
|
||||
TomlCheckerRoot(const TomlCheckerRoot &) = delete;
|
||||
TomlCheckerRoot(TomlCheckerRoot &&) = delete;
|
||||
|
||||
@@ -175,7 +199,7 @@ class TomlCheckerRoot {
|
||||
if (check_root) {
|
||||
for (const auto &itr : m_root.as_table()) {
|
||||
if (!m_visisted.contains(itr.first)) {
|
||||
throw std::runtime_error(format_key_error("Unknown key '" + itr.first + "'", itr.first, itr.second));
|
||||
throw_key_error("Unknown key '" + itr.first + "'", itr.first, itr.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -185,7 +209,7 @@ class TomlCheckerRoot {
|
||||
}
|
||||
};
|
||||
|
||||
Project::Project(const Project *parent, const std::string &path, bool build) {
|
||||
Project::Project(const Project *parent, const std::string &path, bool build) : parent(parent) {
|
||||
const auto toml_path = fs::path(path) / "cmake.toml";
|
||||
if (!fs::exists(toml_path)) {
|
||||
throw std::runtime_error("File not found '" + toml_path.string() + "'");
|
||||
@@ -203,7 +227,7 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
|
||||
cmake.required("version", cmake_version);
|
||||
|
||||
if (cmake.contains("bin-dir")) {
|
||||
throw std::runtime_error("bin-dir has been renamed to build-dir");
|
||||
throw_key_error("bin-dir has been renamed to build-dir", "bin-dir", cmake.find("bin-dir"));
|
||||
}
|
||||
|
||||
cmake.optional("build-dir", build_dir);
|
||||
@@ -241,7 +265,8 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
|
||||
conditions["bsd"] = R"cmake(CMAKE_SYSTEM_NAME MATCHES "BSD")cmake";
|
||||
conditions["linux"] = conditions["lunix"] = R"cmake(CMAKE_SYSTEM_NAME MATCHES "Linux")cmake";
|
||||
conditions["gcc"] = R"cmake(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "GNU")cmake";
|
||||
conditions["clang"] = R"cmake(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_C_COMPILER_ID MATCHES "Clang")cmake";
|
||||
conditions["clang"] =
|
||||
R"cmake((CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND NOT CMAKE_CXX_COMPILER_FRONTEND_VARIANT MATCHES "^MSVC$") OR (CMAKE_C_COMPILER_ID MATCHES "Clang" AND NOT CMAKE_C_COMPILER_FRONTEND_VARIANT MATCHES "^MSVC$"))cmake";
|
||||
conditions["msvc"] = R"cmake(MSVC)cmake";
|
||||
conditions["root"] = R"cmake(CMKR_ROOT_PROJECT)cmake";
|
||||
conditions["x64"] = R"cmake(CMAKE_SIZEOF_VOID_P EQUAL 8)cmake";
|
||||
@@ -264,11 +289,27 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
|
||||
project.optional("version", project_version);
|
||||
project.optional("description", project_description);
|
||||
project.optional("languages", project_languages);
|
||||
project.optional("allow-unknown-languages", project_allow_unknown_languages);
|
||||
project.optional("cmake-before", cmake_before);
|
||||
project.optional("cmake-after", cmake_after);
|
||||
project.optional("include-before", include_before);
|
||||
project.optional("include-after", include_after);
|
||||
project.optional("subdirs", project_subdirs);
|
||||
|
||||
std::string msvc_runtime;
|
||||
project.optional("msvc-runtime", msvc_runtime);
|
||||
if (!msvc_runtime.empty()) {
|
||||
project_msvc_runtime = parse_msvcRuntimeType(msvc_runtime);
|
||||
if (project_msvc_runtime == msvc_last) {
|
||||
std::string error = "Unknown runtime '" + msvc_runtime + "'\n";
|
||||
error += "Available types:\n";
|
||||
for (std::string type_name : msvcRuntimeTypeNames) {
|
||||
error += " - " + type_name + "\n";
|
||||
}
|
||||
error.pop_back(); // Remove last newline
|
||||
throw_key_error(error, msvc_runtime, project.find("msvc-runtime"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (checker.contains("subdir")) {
|
||||
@@ -288,32 +329,42 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
|
||||
}
|
||||
}
|
||||
|
||||
if (checker.contains("settings")) {
|
||||
if (checker.contains("variables")) {
|
||||
using set_map = tsl::ordered_map<std::string, TomlBasicValue>;
|
||||
const auto &sets = toml::find<set_map>(toml, "settings");
|
||||
for (const auto &itr : sets) {
|
||||
Setting s;
|
||||
auto vars = toml::find<set_map>(toml, "variables");
|
||||
if (checker.contains("settings")) {
|
||||
print_key_warning("[settings] has been renamed to [variables]", "settings", toml.at("settings"));
|
||||
const auto &sets = toml::find<set_map>(toml, "settings");
|
||||
for (const auto &itr : sets) {
|
||||
if (!vars.insert(itr).second) {
|
||||
throw_key_error("Key '" + itr.first + "' shadows existing variable", itr.first, itr.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &itr : vars) {
|
||||
Variable s;
|
||||
s.name = itr.first;
|
||||
const auto &value = itr.second;
|
||||
if (value.is_boolean()) {
|
||||
s.val = value.as_boolean();
|
||||
s.value = value.as_boolean();
|
||||
} else if (value.is_string()) {
|
||||
s.val = value.as_string();
|
||||
s.value = value.as_string();
|
||||
} else {
|
||||
auto &setting = checker.create(value);
|
||||
setting.optional("comment", s.comment);
|
||||
setting.optional("help", s.help);
|
||||
if (setting.contains("value")) {
|
||||
const auto &v = setting.find("value");
|
||||
if (v.is_boolean()) {
|
||||
s.val = v.as_boolean();
|
||||
s.value = v.as_boolean();
|
||||
} else {
|
||||
s.val = v.as_string();
|
||||
s.value = v.as_string();
|
||||
}
|
||||
}
|
||||
setting.optional("cache", s.cache);
|
||||
setting.optional("force", s.force);
|
||||
}
|
||||
settings.push_back(s);
|
||||
variables.push_back(s);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,13 +376,37 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
|
||||
o.name = itr.first;
|
||||
const auto &value = itr.second;
|
||||
if (value.is_boolean()) {
|
||||
o.val = value.as_boolean();
|
||||
} else {
|
||||
o.value = value.as_boolean();
|
||||
} else if (value.is_string()) {
|
||||
auto str = std::string(value.as_string());
|
||||
if (str == "root") {
|
||||
o.value = std::string("${CMKR_ROOT_PROJECT}");
|
||||
} else {
|
||||
throw_key_error("Unsupported option value '" + str + "'", str, value);
|
||||
}
|
||||
} else if (value.is_table()) {
|
||||
auto &option = checker.create(value);
|
||||
option.optional("comment", o.comment);
|
||||
option.optional("value", o.val);
|
||||
option.optional("help", o.help);
|
||||
if (option.contains("value")) {
|
||||
const auto &ovalue = option.find("value");
|
||||
if (ovalue.is_boolean()) {
|
||||
o.value = ovalue.as_boolean();
|
||||
} else if (ovalue.is_string()) {
|
||||
auto str = std::string(ovalue.as_string());
|
||||
if (str == "root") {
|
||||
o.value = std::string("${CMKR_ROOT_PROJECT}");
|
||||
} else {
|
||||
throw_key_error("Unsupported option value '" + str + "'", str, value);
|
||||
}
|
||||
} else {
|
||||
throw_key_error(toml::concat_to_string("Unsupported value type: ", ovalue.type()), "value", value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw_key_error(toml::concat_to_string("Unsupported value type: ", itr.second.type()), itr.first, itr.second);
|
||||
}
|
||||
options.push_back(o);
|
||||
conditions.emplace(o.name, o.name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,7 +498,7 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
|
||||
} else if (is_cmake_arg(key)) {
|
||||
// allow passthrough of ExternalProject options
|
||||
} else if (!c.visisted(key)) {
|
||||
throw std::runtime_error(format_key_error("Unknown key '" + argItr.first + "'", argItr.first, argItr.second));
|
||||
throw_key_error("Unknown key '" + argItr.first + "'", argItr.first, argItr.second);
|
||||
}
|
||||
|
||||
// Make sure not to emit keys like "condition" in the FetchContent call
|
||||
@@ -438,7 +513,7 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
|
||||
}
|
||||
|
||||
if (checker.contains("bin")) {
|
||||
throw std::runtime_error("[[bin]] has been renamed to [[target]]");
|
||||
throw_key_error("[[bin]] has been renamed to [target.<name>]", "", toml.at("bin"));
|
||||
}
|
||||
|
||||
auto parse_target = [&](const std::string &name, TomlChecker &t, bool isTemplate) {
|
||||
@@ -477,12 +552,21 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
|
||||
}
|
||||
}
|
||||
error.pop_back(); // Remove last newline
|
||||
throw std::runtime_error(format_key_error(error, target.type_name, t.find("type")));
|
||||
throw_key_error(error, target.type_name, t.find("type"));
|
||||
}
|
||||
|
||||
t.optional("headers", target.headers);
|
||||
t.optional("sources", target.sources);
|
||||
|
||||
// Merge the headers into the sources
|
||||
ConditionVector headers;
|
||||
t.optional("headers", headers);
|
||||
for (const auto &itr : headers) {
|
||||
auto &dest = target.sources[itr.first];
|
||||
for (const auto &jtr : itr.second) {
|
||||
dest.push_back(jtr);
|
||||
}
|
||||
}
|
||||
|
||||
t.optional("compile-definitions", target.compile_definitions);
|
||||
t.optional("private-compile-definitions", target.private_compile_definitions);
|
||||
|
||||
@@ -507,10 +591,32 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
|
||||
t.optional("precompile-headers", target.precompile_headers);
|
||||
t.optional("private-precompile-headers", target.private_precompile_headers);
|
||||
|
||||
if (!target.headers.empty()) {
|
||||
auto &sources = target.sources.nth(0).value();
|
||||
const auto &headers = target.headers.nth(0)->second;
|
||||
sources.insert(sources.end(), headers.begin(), headers.end());
|
||||
Condition<std::string> msvc_runtime;
|
||||
t.optional("msvc-runtime", msvc_runtime);
|
||||
for (const auto &condItr : msvc_runtime) {
|
||||
switch (parse_msvcRuntimeType(condItr.second)) {
|
||||
case msvc_dynamic:
|
||||
target.properties[condItr.first]["MSVC_RUNTIME_LIBRARY"] = "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL";
|
||||
break;
|
||||
case msvc_static:
|
||||
target.properties[condItr.first]["MSVC_RUNTIME_LIBRARY"] = "MultiThreaded$<$<CONFIG:Debug>:Debug>";
|
||||
break;
|
||||
default: {
|
||||
std::string error = "Unknown runtime '" + condItr.second + "'\n";
|
||||
error += "Available types:\n";
|
||||
for (std::string type_name : msvcRuntimeTypeNames) {
|
||||
error += " - " + type_name + "\n";
|
||||
}
|
||||
error.pop_back(); // Remove last newline
|
||||
const TomlBasicValue *report;
|
||||
if (condItr.first.empty()) {
|
||||
report = &t.find("msvc-runtime");
|
||||
} else {
|
||||
report = &t.find(condItr.first).as_table().find("msvc-runtime").value();
|
||||
}
|
||||
throw_key_error(error, condItr.second, *report);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t.optional("condition", target.condition);
|
||||
@@ -564,13 +670,13 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
|
||||
|
||||
for (const auto &type_name : targetTypeNames) {
|
||||
if (name == type_name) {
|
||||
throw std::runtime_error(format_key_error("Reserved template name '" + name + "'", name, itr.second));
|
||||
throw_key_error("Reserved template name '" + name + "'", name, itr.second);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &tmplate : templates) {
|
||||
if (name == tmplate.outline.name) {
|
||||
throw std::runtime_error(format_key_error("Template '" + name + "' already defined", name, itr.second));
|
||||
throw_key_error("Template '" + name + "' already defined", name, itr.second);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -579,6 +685,7 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
|
||||
|
||||
t.optional("add-function", tmplate.add_function);
|
||||
t.optional("pass-sources-to-add-function", tmplate.pass_sources_to_add_function);
|
||||
t.optional("pass-sources", tmplate.pass_sources_to_add_function);
|
||||
|
||||
templates.push_back(tmplate);
|
||||
}
|
||||
@@ -645,7 +752,7 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
|
||||
package.features.emplace_back(feature);
|
||||
}
|
||||
} else {
|
||||
throw std::runtime_error("Invalid vcpkg package '" + package_str + "'");
|
||||
throw_key_error("Invalid package name '" + package_str + "'", "packages", p);
|
||||
}
|
||||
vcpkg.packages.emplace_back(std::move(package));
|
||||
}
|
||||
@@ -654,6 +761,33 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
|
||||
checker.check(conditions, true);
|
||||
}
|
||||
|
||||
const Project *Project::root() const {
|
||||
auto root = this;
|
||||
while (root->parent != nullptr)
|
||||
root = root->parent;
|
||||
return root;
|
||||
}
|
||||
|
||||
bool Project::cmake_minimum_version(int major, int minor) const {
|
||||
// NOTE: this code is like pulling teeth, sorry
|
||||
auto root_version = root()->cmake_version;
|
||||
puts(root_version.c_str());
|
||||
auto range_index = root_version.find("...");
|
||||
if (range_index != std::string::npos) {
|
||||
root_version.resize(range_index);
|
||||
}
|
||||
|
||||
auto period_index = root_version.find('.');
|
||||
auto root_major = atoi(root_version.substr(0, period_index).c_str());
|
||||
int root_minor = 0;
|
||||
if (period_index != std::string::npos) {
|
||||
auto end_index = root_version.find('.', period_index + 1);
|
||||
root_minor = atoi(root_version.substr(period_index + 1, end_index).c_str());
|
||||
}
|
||||
|
||||
return std::tie(root_major, root_minor) >= std::tie(major, minor);
|
||||
}
|
||||
|
||||
bool is_root_path(const std::string &path) {
|
||||
const auto toml_path = fs::path(path) / "cmake.toml";
|
||||
if (!fs::exists(toml_path)) {
|
||||
|
||||
Generated
+12
@@ -88,3 +88,15 @@ add_test(
|
||||
build
|
||||
)
|
||||
|
||||
if(MSVC) # msvc
|
||||
add_test(
|
||||
NAME
|
||||
msvc-runtime
|
||||
WORKING_DIRECTORY
|
||||
"${CMAKE_CURRENT_LIST_DIR}/msvc-runtime"
|
||||
COMMAND
|
||||
"$<TARGET_FILE:cmkr>"
|
||||
build
|
||||
)
|
||||
|
||||
endif()
|
||||
|
||||
@@ -45,3 +45,10 @@ name = "templates"
|
||||
working-directory = "templates"
|
||||
command = "$<TARGET_FILE:cmkr>"
|
||||
arguments = ["build"]
|
||||
|
||||
[[test]]
|
||||
condition = "msvc"
|
||||
name = "msvc-runtime"
|
||||
working-directory = "msvc-runtime"
|
||||
command = "$<TARGET_FILE:cmkr>"
|
||||
arguments = ["build"]
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
int main() { }
|
||||
int main() {
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <Windows.h>
|
||||
|
||||
void foo() { }
|
||||
void foo() {
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
#include <cstdio>
|
||||
#include <tuple>
|
||||
|
||||
int main()
|
||||
{
|
||||
int main() {
|
||||
auto tpl = std::make_tuple(1, 2);
|
||||
printf("Hello from C++11 %d\n", std::get<0>(tpl));
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
#include <fmt/core.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
int main() {
|
||||
fmt::print("Hello, world!\n");
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace mylib
|
||||
{
|
||||
namespace mylib {
|
||||
std::string message();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include <mylib/mylib.hpp>
|
||||
|
||||
std::string mylib::message()
|
||||
{
|
||||
std::string mylib::message() {
|
||||
return "cmkr is awesome!";
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
namespace mylib
|
||||
{
|
||||
static const char* version() { return "v1.0"; }
|
||||
namespace mylib {
|
||||
static const char *version() {
|
||||
return "v1.0";
|
||||
}
|
||||
} // namespace mylib
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "mylib/mylib.hpp"
|
||||
|
||||
int main()
|
||||
{
|
||||
printf("mylib version: %s\n", mylib::version())
|
||||
}
|
||||
int main() {
|
||||
printf("mylib version: %s\n", mylib::version());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
[project]
|
||||
name = "msvc-runtime"
|
||||
description = "Static MSVC runtime"
|
||||
msvc-runtime = "static"
|
||||
|
||||
# This target will compile with a static runtime
|
||||
[target.static-runtime]
|
||||
type = "executable"
|
||||
sources = ["src/main.cpp"]
|
||||
|
||||
# This target overrides the [project].msvc-runtime
|
||||
[target.dynamic-runtime]
|
||||
type = "executable"
|
||||
sources = ["src/main.cpp"]
|
||||
msvc-runtime = "dynamic"
|
||||
@@ -0,0 +1,5 @@
|
||||
#include <cstdio>
|
||||
|
||||
int main() {
|
||||
puts("Hello from cmkr(msvc-static)!");
|
||||
}
|
||||
@@ -7,7 +7,7 @@ description = "Dependencies from vcpkg"
|
||||
# See https://github.com/microsoft/vcpkg/releases for vcpkg versions
|
||||
# See https://vcpkg.io/en/packages.html for available packages
|
||||
[vcpkg]
|
||||
version = "2021.05.12"
|
||||
version = "2022.11.14"
|
||||
packages = ["fmt"]
|
||||
|
||||
[find-package]
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include <fmt/core.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
int main() {
|
||||
fmt::print("Hello, world!\n");
|
||||
}
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
# Disable clang-format in this folder
|
||||
DisableFormat: true
|
||||
SortIncludes: Never
|
||||
Reference in New Issue
Block a user