Compare commits

...

46 Commits

Author SHA1 Message Date
Duncan Ogilvie c76956450a Initial draft of [package] support 2022-03-20 21:19:07 +01:00
Duncan Ogilvie 1596a8143d Bump to 0.2.12 2022-03-06 06:07:55 +01:00
Duncan Ogilvie 9b0f18ee94 Fix a bug in fetch-content generation 2022-03-06 06:05:17 +01:00
Duncan Ogilvie 887086dc05 Credits 2022-02-23 21:32:58 +01:00
Duncan Ogilvie edc8e1e02e Bump to 0.2.11 2022-02-12 04:42:19 +01:00
Duncan Ogilvie 1f6e31e0ef Add optional CMKR_COMMIT_HASH for the paranoid 2022-02-12 04:39:34 +01:00
Duncan Ogilvie 4de1500bb3 Refactor cmake injection 2022-02-12 04:14:47 +01:00
Duncan Ogilvie 85370968f8 Improved fetch-content support
Closes #38
2022-02-12 04:05:51 +01:00
Duncan Ogilvie 06a4c04df6 Add support for 'shallow' in fetch-content 2022-02-12 03:10:25 +01:00
Duncan Ogilvie fef2a0215b Error when recursively globbing in the project root
Closes #48
2022-02-12 03:03:57 +01:00
Duncan Ogilvie fc74a5e802 Bump to 0.2.10 2022-02-12 02:45:10 +01:00
Duncan Ogilvie e48d402e21 Improve version bumping script 2022-02-12 02:44:45 +01:00
Duncan Ogilvie f6a245618e Merge pull request #53 from build-cpp/better-validation
Better TOML validation
2022-02-12 02:44:05 +01:00
Duncan Ogilvie c713606fd4 Error when using an unknown key in the root of the TOML
[error] Unknown key 'fetch-cntent'
 --> cmake.toml:2
   |
 2 | [fetch-cntent.blah]
   |  ~~~~~~~~~~~~
2022-02-12 02:19:37 +01:00
Duncan Ogilvie fe9b6587a9 Error when trying to parse an empty cmake.toml file 2022-02-12 02:07:30 +01:00
Duncan Ogilvie a2f8d9e5c9 Bump to 0.2.9 2022-02-11 02:51:18 +01:00
Duncan Ogilvie c7925e7110 Merge pull request #52 from build-cpp/fix-options
Do not omit non-optional documentation in option()
2022-02-11 02:49:21 +01:00
Duncan Ogilvie c778a5fe2b Do not omit non-optional documentation in option() 2022-02-11 02:42:20 +01:00
Duncan Ogilvie 16b2cbbb57 Add sitemap.xml and robots.txt 2022-02-10 01:20:54 +01:00
Duncan Ogilvie 2f5c7823be Redirect /getting-started to https://cmkr.build 2022-02-10 00:54:32 +01:00
Duncan Ogilvie a29b6a8aaa SEO-friendly permalink 2022-02-10 00:15:28 +01:00
Duncan Ogilvie 5ac8b15c9a Bump to 0.2.8 2022-02-02 17:25:10 +01:00
Duncan Ogilvie e7d9faa6bb Merge pull request #51 from cursey/improvement/install-component
Allow specifying install component name
2022-01-29 11:31:29 +01:00
cursey 6f8b07a801 Allow specifying install component name 2022-01-28 17:16:19 -08:00
Duncan Ogilvie e8c6ccb698 Minor improvements to bootstrapping code 2022-01-26 01:07:18 +01:00
Duncan Ogilvie 539ebc8f4b Host documentation on https://cmkr.build 2022-01-14 18:06:04 +01:00
Duncan Ogilvie 37ca385673 Show vcpkg version when configuring 2022-01-14 13:42:03 +01:00
Duncan Ogilvie 8ae029dab1 Bump to 0.2.7 2022-01-14 01:04:11 +01:00
Duncan Ogilvie fb16cc34b1 Fix a regression with invalid names being generated in vcpkg.json 2022-01-14 01:03:37 +01:00
Duncan Ogilvie 47703871e1 Bump to 0.2.6 2022-01-13 00:35:13 +01:00
Duncan Ogilvie a8d6b15dcb Merge pull request #47 from cursey/templates
Add support for target templates
2022-01-13 00:33:05 +01:00
Duncan Ogilvie 87b3c7ec6c Document template targets 2022-01-13 00:24:04 +01:00
Duncan Ogilvie d905be1d13 Final touches 2022-01-12 23:57:14 +01:00
Duncan Ogilvie 8d13ccbeaa Delete driver test to add later 2022-01-12 23:56:53 +01:00
cursey bcbc9d2b20 Remove use of std::make_unique for C++11 compliance 2022-01-11 00:12:02 -08:00
cursey 05324f593b Improve code reuse for target cmds 2022-01-11 00:04:01 -08:00
cursey 2b7ee72e86 Separate template parsing and generation
* Reuse type for specifying template to use
* Add support for template add-function
* Add support for template pass-sources-to-add-function
2022-01-10 23:29:26 -08:00
cursey 61d5e64d87 Fix parser expecting to always find templates 2022-01-10 18:59:34 -08:00
cursey eed1e38407 Add support for target templates 2022-01-10 18:59:33 -08:00
Duncan Ogilvie 61851d2bee Bump to 0.2.5 2022-01-07 19:45:08 +01:00
Duncan Ogilvie c0d665095e Add script to automatically bump the version
Closes #44
2022-01-07 19:44:33 +01:00
Duncan Ogilvie 6c1e388391 Add root, x64 and x32 conditions 2021-12-30 14:27:07 +01:00
Duncan Ogilvie 04905371b8 Bump to 0.2.4 2021-12-30 02:45:02 +01:00
Duncan Ogilvie 5a6ac44fcf Merge pull request #46 from build-cpp/expanded-conditions
Support conditions everywhere
2021-12-30 02:44:02 +01:00
Duncan Ogilvie 9db82dfd05 Update toml reference 2021-12-30 02:43:17 +01:00
Duncan Ogilvie 459ebb14b5 Support conditions everywhere
Closes #36
2021-12-30 02:35:59 +01:00
29 changed files with 941 additions and 351 deletions
+2 -2
View File
@@ -35,7 +35,7 @@ jobs:
- name: Test - name: Test
run: | run: |
cd build/tests cd build/tests
ctest -C ${{ env.BUILD_TYPE }} ctest -C ${{ env.BUILD_TYPE }} --verbose
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
@@ -50,7 +50,7 @@ jobs:
string: ${{ runner.os }} string: ${{ runner.os }}
- name: Compress artifacts - name: Compress artifacts
uses: papeloto/action-zip@v1 uses: vimtor/action-zip@v1
with: with:
files: install/bin/ files: install/bin/
dest: ${{ github.event.repository.name }}-${{ steps.osname.outputs.lowercase }}.zip dest: ${{ github.event.repository.name }}-${{ steps.osname.outputs.lowercase }}.zip
+1 -2
View File
@@ -25,7 +25,7 @@ project(cmkr
LANGUAGES LANGUAGES
CXX CXX
VERSION VERSION
0.2.3 0.2.12
DESCRIPTION DESCRIPTION
"CMakeLists generator from TOML" "CMakeLists generator from TOML"
) )
@@ -86,7 +86,6 @@ list(APPEND cmkr_SOURCES
"include/arguments.hpp" "include/arguments.hpp"
"include/build.hpp" "include/build.hpp"
"include/cmake_generator.hpp" "include/cmake_generator.hpp"
"include/enum_helper.hpp"
"include/error.hpp" "include/error.hpp"
"include/fs.hpp" "include/fs.hpp"
"include/help.hpp" "include/help.hpp"
+1
View File
@@ -69,3 +69,4 @@ arguments:
- https://github.com/ToruNiina/toml11 - https://github.com/ToruNiina/toml11
- https://github.com/mpark/variant - https://github.com/mpark/variant
- https://www.svgrepo.com/svg/192268/hammer - https://www.svgrepo.com/svg/192268/hammer
- https://github.com/can1357 for buying `cmkr.build` ❤️
+1 -1
View File
@@ -4,7 +4,7 @@ cmkr-include = false
[project] [project]
name = "cmkr" name = "cmkr"
version = "0.2.3" version = "0.2.12"
description = "CMakeLists generator from TOML" description = "CMakeLists generator from TOML"
languages = ["CXX"] languages = ["CXX"]
subdirs = ["third_party", "tests"] subdirs = ["third_party", "tests"]
+78
View File
@@ -0,0 +1,78 @@
cmake_minimum_required(VERSION 3.20)
if(NOT CMAKE_SCRIPT_MODE_FILE)
message(FATAL_ERROR "Usage: cmake -P bump_version.cmake [1.2.3]")
endif()
if(NOT EXISTS "${CMAKE_SOURCE_DIR}/cmake.toml")
message(FATAL_ERROR "Cannot find cmake.toml")
endif()
if(NOT EXISTS "${CMAKE_SOURCE_DIR}/cmake/cmkr.cmake")
message(FATAL_ERROR "Cannot find cmkr.cmake")
endif()
# Validate branch
find_package(Git REQUIRED)
execute_process(COMMAND "${GIT_EXECUTABLE}" branch --show-current OUTPUT_VARIABLE GIT_BRANCH)
string(STRIP "${GIT_BRANCH}" GIT_BRANCH)
if(NOT GIT_BRANCH STREQUAL "main")
message(FATAL_ERROR "You need to be on the main branch, you are on: ${GIT_BRANCH}")
endif()
file(READ "${CMAKE_SOURCE_DIR}/cmake.toml" CMAKE_TOML)
string(FIND "${CMAKE_TOML}" "[project]" PROJECT_INDEX)
string(SUBSTRING "${CMAKE_TOML}" ${PROJECT_INDEX} -1 CMAKE_TOML_PROJECT)
set(SEMVER_REGEX "([0-9]+)\\.([0-9]+)\\.([0-9]+)")
set(VERSION_REGEX "version = \"${SEMVER_REGEX}\"")
if(CMAKE_TOML_PROJECT MATCHES "${VERSION_REGEX}")
set(MAJOR "${CMAKE_MATCH_1}")
set(MINOR "${CMAKE_MATCH_2}")
set(PATCH "${CMAKE_MATCH_3}")
set(OLDVERSION "${MAJOR}.${MINOR}.${PATCH}")
else()
message(FATAL_ERROR "Failed to match semantic version in cmake.toml")
endif()
if(CMAKE_ARGV3)
if(NOT CMAKE_ARGV3 MATCHES "${SEMVER_REGEX}")
message(FATAL_ERROR "Invalid semantic version number '${CMAKE_ARGV3}'")
endif()
set(NEWVERSION "${CMAKE_ARGV3}")
else()
math(EXPR NEWPATCH "${PATCH} + 1")
set(NEWVERSION "${MAJOR}.${MINOR}.${NEWPATCH}")
endif()
message(STATUS "Version ${OLDVERSION} -> ${NEWVERSION}")
find_program(CMKR_EXECUTABLE "cmkr" PATHS "${CMAKE_SOURCE_DIR}/build" PATH_SUFFIXES Debug Release RelWithDebInfo MinSizeRel NO_CACHE REQUIRED)
message(STATUS "Found cmkr: ${CMKR_EXECUTABLE}")
# Replace version in cmake.toml
string(REPLACE "version = \"${OLDVERSION}\"" "version = \"${NEWVERSION}\"" CMAKE_TOML "${CMAKE_TOML}")
file(CONFIGURE
OUTPUT "${CMAKE_SOURCE_DIR}/cmake.toml"
CONTENT "${CMAKE_TOML}"
@ONLY
NEWLINE_STYLE LF
)
# Run cmkr gen
execute_process(COMMAND "${CMKR_EXECUTABLE}" gen RESULT_VARIABLE CMKR_EXEC_RESULT)
if(NOT CMKR_EXEC_RESULT EQUAL 0)
message(FATAL_ERROR "cmkr gen failed (exit code ${CMKR_EXEC_RESULT})")
endif()
# Replace version in cmkr.cmake
file(READ "${CMAKE_SOURCE_DIR}/cmake/cmkr.cmake" CMKR_CMAKE)
string(REGEX REPLACE "CMKR_TAG \"[^\"]+\"" "CMKR_TAG \"v${NEWVERSION}\"" CMKR_CMAKE "${CMKR_CMAKE}")
file(CONFIGURE
OUTPUT "${CMAKE_SOURCE_DIR}/cmake/cmkr.cmake"
CONTENT "${CMKR_CMAKE}"
@ONLY
NEWLINE_STYLE LF
)
# Print git commands
message(STATUS "Git commands to create new version:\ngit commit -a -m \"Bump to ${NEWVERSION}\"\ngit tag v${NEWVERSION}\ngit push origin main v${NEWVERSION}")
+21 -7
View File
@@ -2,7 +2,8 @@ include_guard()
# Change these defaults to point to your infrastructure if desired # 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_REPO "https://github.com/build-cpp/cmkr" CACHE STRING "cmkr git repository" FORCE)
set(CMKR_TAG "v0.2.3" CACHE STRING "cmkr git tag (this needs to be available forever)" FORCE) set(CMKR_TAG "v0.2.12" 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 # To bootstrap/generate a cmkr project: cmake -P cmkr.cmake
if(CMAKE_SCRIPT_MODE_FILE) if(CMAKE_SCRIPT_MODE_FILE)
@@ -15,6 +16,7 @@ endif()
set(CMKR_EXECUTABLE "" CACHE FILEPATH "cmkr executable") set(CMKR_EXECUTABLE "" CACHE FILEPATH "cmkr executable")
set(CMKR_SKIP_GENERATION OFF CACHE BOOL "skip automatic cmkr generation") set(CMKR_SKIP_GENERATION OFF CACHE BOOL "skip automatic cmkr generation")
set(CMKR_BUILD_TYPE "Debug" CACHE STRING "cmkr build configuration") set(CMKR_BUILD_TYPE "Debug" CACHE STRING "cmkr build configuration")
mark_as_advanced(CMKR_REPO CMKR_TAG CMKR_COMMIT_HASH CMKR_EXECUTABLE CMKR_SKIP_GENERATION CMKR_BUILD_TYPE)
# Disable cmkr if generation is disabled # Disable cmkr if generation is disabled
if(DEFINED ENV{CI} OR CMKR_SKIP_GENERATION OR CMKR_BUILD_SKIP_GENERATION) if(DEFINED ENV{CI} OR CMKR_SKIP_GENERATION OR CMKR_BUILD_SKIP_GENERATION)
@@ -62,6 +64,8 @@ if(DEFINED ENV{CMKR_CACHE} AND EXISTS "$ENV{CMKR_CACHE}")
if(NOT CMKR_DIRECTORY_PREFIX MATCHES "\\/$") if(NOT CMKR_DIRECTORY_PREFIX MATCHES "\\/$")
set(CMKR_DIRECTORY_PREFIX "${CMKR_DIRECTORY_PREFIX}/") set(CMKR_DIRECTORY_PREFIX "${CMKR_DIRECTORY_PREFIX}/")
endif() endif()
# Build in release mode for the cache
set(CMKR_BUILD_TYPE "Release")
else() else()
set(CMKR_DIRECTORY_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/_cmkr_") set(CMKR_DIRECTORY_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/_cmkr_")
endif() endif()
@@ -69,7 +73,7 @@ set(CMKR_DIRECTORY "${CMKR_DIRECTORY_PREFIX}${CMKR_TAG}")
set(CMKR_CACHED_EXECUTABLE "${CMKR_DIRECTORY}/bin/${CMKR_EXECUTABLE_NAME}") set(CMKR_CACHED_EXECUTABLE "${CMKR_DIRECTORY}/bin/${CMKR_EXECUTABLE_NAME}")
# Handle upgrading logic # Handle upgrading logic
if(CMKR_EXECUTABLE AND NOT CMKR_CACHED_EXECUTABLE STREQUAL CMKR_EXECUTABLE) if(CMKR_EXECUTABLE AND NOT CMKR_CACHED_EXECUTABLE STREQUAL CMKR_EXECUTABLE)
if(CMKR_EXECUTABLE MATCHES "^${CMAKE_CURRENT_BINARY_DIR}/_cmkr") if(CMKR_EXECUTABLE MATCHES "^${CMAKE_CURRENT_BINARY_DIR}/_cmkr")
if(DEFINED ENV{CMKR_CACHE} AND EXISTS "$ENV{CMKR_CACHE}") if(DEFINED ENV{CMKR_CACHE} AND EXISTS "$ENV{CMKR_CACHE}")
message(AUTHOR_WARNING "[cmkr] Switching to cached cmkr: '${CMKR_CACHED_EXECUTABLE}'") message(AUTHOR_WARNING "[cmkr] Switching to cached cmkr: '${CMKR_CACHED_EXECUTABLE}'")
@@ -98,7 +102,7 @@ elseif(NOT CMKR_EXECUTABLE AND EXISTS "${CMKR_CACHED_EXECUTABLE}")
else() else()
set(CMKR_EXECUTABLE "${CMKR_CACHED_EXECUTABLE}" CACHE FILEPATH "Full path to cmkr executable" FORCE) set(CMKR_EXECUTABLE "${CMKR_CACHED_EXECUTABLE}" CACHE FILEPATH "Full path to cmkr executable" FORCE)
message(VERBOSE "[cmkr] Bootstrapping '${CMKR_EXECUTABLE}'") message(VERBOSE "[cmkr] Bootstrapping '${CMKR_EXECUTABLE}'")
message(STATUS "[cmkr] Fetching cmkr...") message(STATUS "[cmkr] Fetching cmkr...")
if(EXISTS "${CMKR_DIRECTORY}") if(EXISTS "${CMKR_DIRECTORY}")
cmkr_exec("${CMAKE_COMMAND}" -E rm -rf "${CMKR_DIRECTORY}") cmkr_exec("${CMAKE_COMMAND}" -E rm -rf "${CMKR_DIRECTORY}")
@@ -112,6 +116,16 @@ else()
${CMKR_REPO} ${CMKR_REPO}
"${CMKR_DIRECTORY}" "${CMKR_DIRECTORY}"
) )
if(CMKR_COMMIT_HASH)
execute_process(
COMMAND "${GIT_EXECUTABLE}" checkout -q "${CMKR_COMMIT_HASH}"
RESULT_VARIABLE CMKR_EXEC_RESULT
WORKING_DIRECTORY "${CMKR_DIRECTORY}"
)
if(NOT CMKR_EXEC_RESULT EQUAL 0)
message(FATAL_ERROR "Tag '${CMKR_TAG}' hash is not '${CMKR_COMMIT_HASH}'")
endif()
endif()
message(STATUS "[cmkr] Building cmkr (using system compiler)...") message(STATUS "[cmkr] Building cmkr (using system compiler)...")
cmkr_exec("${CMAKE_COMMAND}" cmkr_exec("${CMAKE_COMMAND}"
--no-warn-unused-cli --no-warn-unused-cli
@@ -202,18 +216,18 @@ macro(cmkr)
# Copy the now-generated CMakeLists.txt to CMakerLists.txt # Copy the now-generated CMakeLists.txt to CMakerLists.txt
# This is done because you cannot include() a file you are currently in # This is done because you cannot include() a file you are currently in
configure_file(CMakeLists.txt "${CMKR_TEMP_FILE}" COPYONLY) configure_file(CMakeLists.txt "${CMKR_TEMP_FILE}" COPYONLY)
# Add the macro required for the hack at the start of the cmkr macro # Add the macro required for the hack at the start of the cmkr macro
set_source_files_properties("${CMKR_TEMP_FILE}" PROPERTIES set_source_files_properties("${CMKR_TEMP_FILE}" PROPERTIES
CMKR_CURRENT_LIST_FILE "${CMAKE_CURRENT_LIST_FILE}" CMKR_CURRENT_LIST_FILE "${CMAKE_CURRENT_LIST_FILE}"
) )
# 'Execute' the newly-generated CMakeLists.txt # 'Execute' the newly-generated CMakeLists.txt
include("${CMKR_TEMP_FILE}") include("${CMKR_TEMP_FILE}")
# Delete the generated file # Delete the generated file
file(REMOVE "${CMKR_TEMP_FILE}") file(REMOVE "${CMKR_TEMP_FILE}")
# Do not execute the rest of the original CMakeLists.txt # Do not execute the rest of the original CMakeLists.txt
return() return()
endif() endif()
+1
View File
@@ -0,0 +1 @@
cmkr.build
+1
View File
@@ -3,6 +3,7 @@ gem "minima", "~> 2.5"
gem "github-pages", group: :jekyll_plugins gem "github-pages", group: :jekyll_plugins
gem "jekyll-remote-theme" gem "jekyll-remote-theme"
gem "jekyll-sitemap", "~> 1.4.0"
platforms :mingw, :x64_mingw, :mswin, :jruby do platforms :mingw, :x64_mingw, :mswin, :jruby do
gem "tzinfo", "~> 1.2" gem "tzinfo", "~> 1.2"
+1
View File
@@ -271,6 +271,7 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
github-pages github-pages
jekyll-remote-theme jekyll-remote-theme
jekyll-sitemap (~> 1.4.0)
just-the-docs just-the-docs
minima (~> 2.5) minima (~> 2.5)
tzinfo (~> 1.2) tzinfo (~> 1.2)
+5 -2
View File
@@ -2,6 +2,7 @@ title: cmkr
plugins: plugins:
- jekyll-remote-theme - jekyll-remote-theme
- jekyll-sitemap
# Automatically deduce dark theme preference https://github.com/pmarsceill/just-the-docs/pull/464 # Automatically deduce dark theme preference https://github.com/pmarsceill/just-the-docs/pull/464
remote_theme: build-cpp/just-the-docs@light-switch remote_theme: build-cpp/just-the-docs@light-switch
@@ -9,7 +10,7 @@ search_enabled: true
color_scheme: light-or-dark color_scheme: light-or-dark
heading_anchors: true heading_anchors: true
aux_links: aux_links:
"cmkr": "GitHub":
- "https://github.com/build-cpp/cmkr" - "https://github.com/build-cpp/cmkr"
aux_links_new_tab: true aux_links_new_tab: true
@@ -18,4 +19,6 @@ gh_edit_link_text: "Edit this page on GitHub."
gh_edit_repository: "https://github.com/build-cpp/cmkr" gh_edit_repository: "https://github.com/build-cpp/cmkr"
gh_edit_branch: "main" gh_edit_branch: "main"
gh_edit_view_mode: "edit" gh_edit_view_mode: "edit"
gh_edit_source: docs gh_edit_source: docs
production_url : https://cmkr.build
+22 -13
View File
@@ -47,6 +47,8 @@ arch64 = "CMAKE_SIZEOF_VOID_P EQUALS 8"
arch32 = "CMAKE_SIZEOF_VOID_P EQUALS 4" arch32 = "CMAKE_SIZEOF_VOID_P EQUALS 4"
``` ```
This will make the `arch64` and `arch32` conditions available with their respective CMake expressions.
You can also prefix most keys with `condition.` to represent a conditional: You can also prefix most keys with `condition.` to represent a conditional:
```toml ```toml
@@ -56,7 +58,7 @@ sources = ["src/main.cpp"]
windows.sources = ["src/windows_specific.cpp"] windows.sources = ["src/windows_specific.cpp"]
``` ```
This will make the `arch64` and `arch32` conditions available with their respective CMake expressions. The following conditions are predefined (you can override them if you desire): The following conditions are predefined (you can override them if you desire):
```toml ```toml
[conditions] [conditions]
@@ -68,13 +70,16 @@ linux = "CMAKE_SYSTEM_NAME MATCHES \"Linux\""
gcc = "CMAKE_CXX_COMPILER_ID STREQUAL \"GNU\" OR CMAKE_C_COMPILER_ID STREQUAL \"GNU\"" 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\" OR CMAKE_C_COMPILER_ID MATCHES \"Clang\""
msvc = "MSVC" msvc = "MSVC"
root = "CMKR_ROOT_PROJECT"
x64 = "CMAKE_SIZEOF_VOID_P EQUAL 8"
x32 = "CMAKE_SIZEOF_VOID_P EQUAL 4"
``` ```
## Subdirectories ## Subdirectories
```toml ```toml
[subdir.mysubdir] [subdir.mysubdir]
condition = "linux" condition = "mycondition"
cmake-before = """ cmake-before = """
message(STATUS "CMake injected before the add_subdirectory() call" message(STATUS "CMake injected before the add_subdirectory() call"
""" """
@@ -101,11 +106,8 @@ To specify package features you can use the following syntax: `imgui[docking-exp
## Packages ## Packages
```toml ```toml
[find-package]
mypackage = { version = "1.0", required = true, config = true, components = ["mycomponent"] }
# Alternative syntax
[find-package.mypackage] [find-package.mypackage]
condition = "mycondition"
version = "1.0" version = "1.0"
required = true required = true
config = true config = true
@@ -117,22 +119,27 @@ components = ["mycomponent"]
**Note**: The `[fetch-content]` feature is unpolished and will likely change in a future release. **Note**: The `[fetch-content]` feature is unpolished and will likely change in a future release.
```toml ```toml
[fetch-content]
gitcontent = { git = "https://github.com/myuser/gitcontent", tag = "v0.1" }
svncontent = { svn = "https://svn-host.com/url", rev = "svn_rev" }
urlcontent = { url = "https://content-host.com/urlcontent.zip", hash = "123123123123" }
# Alternative syntax
[fetch-content.gitcontent] [fetch-content.gitcontent]
condition = "mycondition"
git = "https://github.com/myuser/gitcontent" git = "https://github.com/myuser/gitcontent"
tag = "v0.1" tag = "v0.1"
[fetch-content.svncontent]
condition = "mycondition"
svn = "https://svn-host.com/url"
rev = "svn_rev"
[fetch-content.urlcontent]
condition = "mycondition"
url = "https://content-host.com/urlcontent.zip"
hash = "123123123123"
``` ```
## Targets ## Targets
```toml ```toml
[target.mytarget] [target.mytarget]
condition = "linux" condition = "mycondition"
alias = "mytarget::mytarget" alias = "mytarget::mytarget"
type = "static" # executable, shared (DLL), static, interface, object, library, custom type = "static" # executable, shared (DLL), static, interface, object, library, custom
headers = ["src/mytarget.h"] headers = ["src/mytarget.h"]
@@ -180,6 +187,7 @@ FOLDER = "MyFolder"
```toml ```toml
# You can declare as many as you want like this, but the name has to be unique # You can declare as many as you want like this, but the name has to be unique
[[test]] [[test]]
condition = "mycondition"
name = "mytest" name = "mytest"
command = "$<TARGET_FILE:mytest>" command = "$<TARGET_FILE:mytest>"
arguments = ["arg1", "arg2"] arguments = ["arg1", "arg2"]
@@ -189,6 +197,7 @@ working-directory = "mytest-dir"
```toml ```toml
[[install]] [[install]]
condition = "mycondition"
targets = ["mytarget", "mytest"] targets = ["mytarget", "mytest"]
destination = ["bin"] destination = ["bin"]
files = ["content/my.png"] files = ["content/my.png"]
+1
View File
@@ -19,6 +19,7 @@ description = "Header-only library"
[target.mylib] [target.mylib]
type = "interface" type = "interface"
include-directories = ["include"] include-directories = ["include"]
compile-features = ["cxx_std_11"]
[target.example] [target.example]
type = "executable" type = "executable"
+40
View File
@@ -0,0 +1,40 @@
---
# Automatically generated from tests/templates/cmake.toml - DO NOT EDIT
layout: default
title: Target templates
permalink: /examples/templates
parent: Examples
nav_order: 7
---
# Target templates
To avoid repeating yourself in targets you can create your own target type (template). All properties of the template are inherited when used as a target type.
```toml
[project]
name = "templates"
description = "Target templates"
[template.app]
type = "executable"
sources = ["src/templates.cpp"]
compile-definitions = ["IS_APP"]
# Unlike interface targets you can also inherit properties
[template.app.properties]
CXX_STANDARD = "11"
CXX_STANDARD_REQUIRED = true
[target.app-a]
type = "app"
compile-definitions = ["APP_A"]
[target.app-b]
type = "app"
compile-definitions = ["APP_B"]
```
**Note**: In most cases you probably want to use an [interface](/examples/interface) target instead.
<sup><sub>This page was automatically generated from [tests/templates/cmake.toml](https://github.com/build-cpp/cmkr/tree/main/tests/templates/cmake.toml).</sub></sup>
+10
View File
@@ -0,0 +1,10 @@
---
layout: null
permalink: /getting-started/
---
<html>
<head>
<meta http-equiv="refresh" content="0;url=https://cmkr.build">
</head>
</html>
-69
View File
@@ -1,69 +0,0 @@
// https://codereview.stackexchange.com/a/14315
#include <algorithm>
#include <iostream>
#include <sstream>
#include <string>
// This is the type that will hold all the strings.
// Each enumeration type will declare its own specialization.
// Any enum that does not have a specialization will generate a compiler error
// indicating that there is no definition of this variable (as there should be
// be no definition of a generic version).
template <typename T>
struct enumStrings {
static char const *data[];
};
// This is a utility type.
// Created automatically. Should not be used directly.
template <typename T>
struct enumRefHolder {
T &enumVal;
enumRefHolder(T &enumVal) : enumVal(enumVal) {}
};
template <typename T>
struct enumConstRefHolder {
T const &enumVal;
enumConstRefHolder(T const &enumVal) : enumVal(enumVal) {}
};
// The next two functions do the actual work of reading/writing an
// enum as a string.
template <typename T>
std::ostream &operator<<(std::ostream &str, enumConstRefHolder<T> const &data) {
return str << enumStrings<T>::data[data.enumVal];
}
template <typename T>
std::istream &operator>>(std::istream &str, enumRefHolder<T> const &data) {
std::string value;
str >> value;
// These two can be made easier to read in C++11
// using std::begin() and std::end()
//
static auto begin = std::begin(enumStrings<T>::data);
static auto end = std::end(enumStrings<T>::data);
auto find = std::find(begin, end, value);
if (find != end) {
data.enumVal = static_cast<T>(std::distance(begin, find));
} else {
throw std::invalid_argument("");
}
return str;
}
// This is the public interface:
// use the ability of function to deduce their template type without
// being explicitly told to create the correct type of enumRefHolder<T>
template <typename T>
enumConstRefHolder<T> enumToString(T const &e) {
return enumConstRefHolder<T>(e);
}
template <typename T>
enumRefHolder<T> enumFromString(T &e) {
return enumRefHolder<T>(e);
}
+39 -7
View File
@@ -8,6 +8,11 @@
namespace cmkr { namespace cmkr {
namespace parser { namespace parser {
template <typename T>
using Condition = tsl::ordered_map<std::string, T>;
using ConditionVector = Condition<std::vector<std::string>>;
struct Setting { struct Setting {
std::string name; std::string name;
std::string comment; std::string comment;
@@ -22,8 +27,9 @@ struct Option {
bool val = false; bool val = false;
}; };
struct Package { struct FindPackage {
std::string name; std::string name;
std::string condition;
std::string version; std::string version;
bool required = true; bool required = true;
bool config = false; bool config = false;
@@ -50,16 +56,16 @@ enum TargetType {
target_interface, target_interface,
target_custom, target_custom,
target_object, target_object,
target_template,
target_last,
}; };
template <typename T> extern const char *targetTypeNames[target_last];
using Condition = tsl::ordered_map<std::string, T>;
using ConditionVector = Condition<std::vector<std::string>>;
struct Target { struct Target {
std::string name; std::string name;
TargetType type = {}; TargetType type = target_last;
std::string type_name;
ConditionVector headers; ConditionVector headers;
ConditionVector sources; ConditionVector sources;
@@ -99,8 +105,15 @@ struct Target {
ConditionVector include_after; ConditionVector include_after;
}; };
struct Template {
Target outline;
std::string add_function;
bool pass_sources_to_add_function = false;
};
struct Test { struct Test {
std::string name; std::string name;
std::string condition;
std::vector<std::string> configurations; std::vector<std::string> configurations;
std::string working_directory; std::string working_directory;
std::string command; std::string command;
@@ -108,16 +121,19 @@ struct Test {
}; };
struct Install { struct Install {
std::string condition;
std::vector<std::string> targets; std::vector<std::string> targets;
std::vector<std::string> files; std::vector<std::string> files;
std::vector<std::string> dirs; std::vector<std::string> dirs;
std::vector<std::string> configs; std::vector<std::string> configs;
std::string destination; std::string destination;
std::string component;
}; };
struct Subdir { struct Subdir {
std::string name; std::string name;
std::string condition; std::string condition;
Condition<std::string> cmake_before; Condition<std::string> cmake_before;
Condition<std::string> cmake_after; Condition<std::string> cmake_after;
ConditionVector include_before; ConditionVector include_before;
@@ -126,7 +142,21 @@ struct Subdir {
struct Content { struct Content {
std::string name; std::string name;
std::string condition;
tsl::ordered_map<std::string, std::string> arguments; tsl::ordered_map<std::string, std::string> arguments;
Condition<std::string> cmake_before;
Condition<std::string> cmake_after;
ConditionVector include_before;
ConditionVector include_after;
};
struct Package {
std::string name;
std::vector<std::string> targets;
std::vector<std::string> headers;
std::vector<std::string> dependencies;
std::string namespace_name;
}; };
struct Project { struct Project {
@@ -153,9 +183,11 @@ struct Project {
ConditionVector include_after; ConditionVector include_after;
std::vector<Setting> settings; std::vector<Setting> settings;
std::vector<Option> options; std::vector<Option> options;
std::vector<Package> packages; std::vector<FindPackage> packages;
Vcpkg vcpkg; Vcpkg vcpkg;
Package package;
std::vector<Content> contents; std::vector<Content> contents;
std::vector<Template> templates;
std::vector<Target> targets; std::vector<Target> targets;
std::vector<Test> tests; std::vector<Test> tests;
std::vector<Install> installs; std::vector<Install> installs;
+320 -116
View File
@@ -6,21 +6,13 @@
#include "fs.hpp" #include "fs.hpp"
#include <cstdio> #include <cstdio>
#include <fstream> #include <fstream>
#include <memory>
#include <sstream> #include <sstream>
#include <stdexcept> #include <stdexcept>
namespace cmkr { namespace cmkr {
namespace gen { namespace gen {
static std::string to_upper(const std::string &str) {
std::string temp;
temp.reserve(str.size());
for (auto c : str) {
temp.push_back(::toupper(c));
}
return temp;
}
static std::string format(const char *format, tsl::ordered_map<std::string, std::string> variables) { static std::string format(const char *format, tsl::ordered_map<std::string, std::string> variables) {
std::string s = format; std::string s = format;
for (const auto &itr : variables) { for (const auto &itr : variables) {
@@ -33,19 +25,23 @@ static std::string format(const char *format, tsl::ordered_map<std::string, std:
return s; return s;
} }
static std::vector<std::string> expand_cmake_path(const fs::path &name, const fs::path &toml_dir) { static std::string extract_suffix(const fs::path &base, const fs::path &full) {
std::vector<std::string> temp; auto fullpath = full.string();
auto base_len = base.string().length();
auto delet = fullpath.substr(base_len + 1, fullpath.length() - base_len);
return delet;
}
auto extract_suffix = [](const fs::path &base, const fs::path &full) { static std::vector<std::string> expand_cmake_path(const fs::path &name, const fs::path &toml_dir, bool root_project) {
auto fullpath = full.string(); std::vector<std::string> temp;
auto base_len = base.string().length();
auto delet = fullpath.substr(base_len + 1, fullpath.length() - base_len);
return delet;
};
auto stem = name.filename().stem().string(); auto stem = name.filename().stem().string();
auto ext = name.extension(); auto ext = name.extension();
if (root_project && stem == "**" && name == name.filename()) {
throw std::runtime_error("Recursive globbing not allowed in project root: " + name.string());
}
if (stem == "*") { if (stem == "*") {
for (const auto &f : fs::directory_iterator(toml_dir / name.parent_path(), fs::directory_options::follow_directory_symlink)) { for (const auto &f : fs::directory_iterator(toml_dir / name.parent_path(), fs::directory_options::follow_directory_symlink)) {
if (!f.is_directory() && f.path().extension() == ext) { if (!f.is_directory() && f.path().extension() == ext) {
@@ -70,11 +66,11 @@ static std::vector<std::string> expand_cmake_path(const fs::path &name, const fs
return temp; return temp;
} }
static std::vector<std::string> expand_cmake_paths(const std::vector<std::string> &sources, const fs::path &toml_dir) { static std::vector<std::string> expand_cmake_paths(const std::vector<std::string> &sources, const fs::path &toml_dir, bool root_project) {
// TODO: add duplicate checking // TODO: add duplicate checking
std::vector<std::string> result; std::vector<std::string> result;
for (const auto &src : sources) { for (const auto &src : sources) {
auto expanded = expand_cmake_path(src, toml_dir); auto expanded = expand_cmake_path(src, toml_dir, root_project);
for (const auto &f : expanded) { for (const auto &f : expanded) {
result.push_back(f); result.push_back(f);
} }
@@ -161,6 +157,10 @@ struct Command {
} }
static std::string quote(const std::string &str) { static std::string quote(const std::string &str) {
// Quote an empty string
if (str.empty()) {
return "\"\"";
}
// Don't quote arguments that don't need quoting // Don't quote arguments that don't need quoting
if (str.find(' ') == std::string::npos && str.find('\"') == std::string::npos && str.find('/') == std::string::npos && if (str.find(' ') == std::string::npos && str.find('\"') == std::string::npos && str.find('/') == std::string::npos &&
str.find(';') == std::string::npos) { str.find(';') == std::string::npos) {
@@ -234,9 +234,16 @@ struct Command {
return true; return true;
} }
template <class T>
static bool is_empty(const T &v) {
return v.empty();
}
static bool is_empty(const char *v) { return *v == '\0'; }
template <class K, class V> template <class K, class V>
bool print_arg(const std::pair<K, V> &kv) { bool print_arg(const std::pair<K, V> &kv) {
if (kv.second.empty()) { if (is_empty(kv.second)) {
return true; return true;
} }
@@ -250,7 +257,7 @@ struct Command {
} }
bool print_arg(const RawArg &arg) { bool print_arg(const RawArg &arg) {
if (arg.arg.empty()) { if (is_empty(arg.arg)) {
return true; return true;
} }
@@ -316,10 +323,10 @@ static std::string tolf(const std::string &str) {
}; };
struct Generator { struct Generator {
Generator(parser::Project &project) : project(project) {} Generator(const parser::Project &project) : project(project) {}
Generator(const Generator &) = delete; Generator(const Generator &) = delete;
parser::Project &project; const parser::Project &project;
std::stringstream ss; std::stringstream ss;
int indent = 0; int indent = 0;
@@ -383,11 +390,7 @@ struct Generator {
for (const auto &itr : value) { for (const auto &itr : value) {
const auto &condition = itr.first; const auto &condition = itr.first;
if (!condition.empty()) { if (!condition.empty()) {
if (project.conditions.count(condition) == 0) { cmd("if", condition)(RawArg(project.conditions.at(condition)));
// TODO: somehow print line number information here?
throw std::runtime_error("Unknown condition '" + condition + "'");
}
cmd("if", condition)(RawArg(project.conditions[condition]));
} }
if (!itr.second.empty()) { if (!itr.second.empty()) {
@@ -401,6 +404,35 @@ struct Generator {
} }
} }
} }
void conditional_includes(const parser::ConditionVector &include) {
handle_condition(include, [this](const std::string &, const std::vector<std::string> &includes) { inject_includes(includes); });
}
void conditional_cmake(const parser::Condition<std::string> &cmake) {
handle_condition(cmake, [this](const std::string &, const std::string &cmake) { inject_cmake(cmake); });
}
};
struct ConditionScope {
Generator &gen;
bool endif = false;
ConditionScope(Generator &gen, const std::string &condition) : gen(gen) {
if (!condition.empty()) {
gen.cmd("if", condition)(RawArg(gen.project.conditions.at(condition)));
endif = true;
}
}
ConditionScope(const ConditionScope &) = delete;
ConditionScope(ConditionScope &&) = delete;
~ConditionScope() {
if (endif) {
gen.cmd("endif")();
}
}
}; };
static bool vcpkg_valid_identifier(const std::string &name) { static bool vcpkg_valid_identifier(const std::string &name) {
@@ -454,6 +486,9 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
throw std::runtime_error("No cmake.toml found!"); throw std::runtime_error("No cmake.toml found!");
} }
// Root project doesn't have a parent
auto root_project = parent_project == nullptr;
parser::Project project(parent_project, path, false); parser::Project project(parent_project, path, false);
Generator gen(project); Generator gen(project);
@@ -462,16 +497,13 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
auto cmd = [&gen](const std::string &command) { return gen.cmd(command); }; auto cmd = [&gen](const std::string &command) { return gen.cmd(command); };
auto comment = [&gen](const std::string &comment) { return gen.comment(comment); }; auto comment = [&gen](const std::string &comment) { return gen.comment(comment); };
auto endl = [&gen]() { gen.endl(); }; auto endl = [&gen]() { gen.endl(); };
auto inject_includes = [&gen](const std::vector<std::string> &includes) { gen.inject_includes(includes); };
auto inject_cmake = [&gen](const std::string &cmake) { gen.inject_cmake(cmake); };
std::string cmkr_url = "https://github.com/build-cpp/cmkr"; std::string cmkr_url = "https://github.com/build-cpp/cmkr";
comment("This file is automatically generated from cmake.toml - DO NOT EDIT"); comment("This file is automatically generated from cmake.toml - DO NOT EDIT");
comment("See " + cmkr_url + " for more information"); comment("See " + cmkr_url + " for more information");
endl(); endl();
// Root project doesn't have a parent if (root_project) {
if (parent_project == nullptr) {
cmd("cmake_minimum_required")("VERSION", project.cmake_version).endl(); cmd("cmake_minimum_required")("VERSION", project.cmake_version).endl();
if (!project.allow_in_tree) { if (!project.allow_in_tree) {
@@ -544,7 +576,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
if (!project.options.empty()) { if (!project.options.empty()) {
comment("Options"); comment("Options");
for (const auto &opt : project.options) { for (const auto &opt : project.options) {
cmd("option")(opt.name, opt.comment, opt.val ? "ON" : "OFF"); cmd("option")(opt.name, RawArg(Command::quote(opt.comment)), opt.val ? "ON" : "OFF");
} }
endl(); endl();
} }
@@ -570,8 +602,8 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
endl(); endl();
} }
gen.handle_condition(project.include_before, [&](const std::string &, const std::vector<std::string> &includes) { inject_includes(includes); }); gen.conditional_includes(project.include_before);
gen.handle_condition(project.cmake_before, [&](const std::string &, const std::string &cmake) { inject_cmake(cmake); }); gen.conditional_cmake(project.cmake_before);
if (!project.project_name.empty()) { if (!project.project_name.empty()) {
auto languages = std::make_pair("LANGUAGES", project.project_languages); auto languages = std::make_pair("LANGUAGES", project.project_languages);
@@ -580,17 +612,22 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
cmd("project")(project.project_name, languages, version, description).endl(); cmd("project")(project.project_name, languages, version, description).endl();
} }
gen.handle_condition(project.include_after, [&](const std::string &, const std::vector<std::string> &includes) { inject_includes(includes); }); gen.conditional_includes(project.include_after);
gen.handle_condition(project.cmake_after, [&](const std::string &, const std::string &cmake) { inject_cmake(cmake); }); gen.conditional_cmake(project.cmake_after);
if (!project.vcpkg.packages.empty()) { if (!project.vcpkg.packages.empty()) {
if (!root_project) {
throw std::runtime_error("[vcpkg] has to be in the project root");
}
// Allow the user to specify a url or derive it from the version // Allow the user to specify a url or derive it from the version
auto url = project.vcpkg.url; auto url = project.vcpkg.url;
auto version_name = url;
if (url.empty()) { if (url.empty()) {
if (project.vcpkg.version.empty()) { if (project.vcpkg.version.empty()) {
throw std::runtime_error("You need either [vcpkg].version or [vcpkg].url"); throw std::runtime_error("You need either [vcpkg].version or [vcpkg].url");
} }
url = "https://github.com/microsoft/vcpkg/archive/refs/tags/" + project.vcpkg.version + ".tar.gz"; url = "https://github.com/microsoft/vcpkg/archive/refs/tags/" + project.vcpkg.version + ".tar.gz";
version_name = project.vcpkg.version;
} }
// Show a nicer error than vcpkg when specifying an invalid package name // Show a nicer error than vcpkg when specifying an invalid package name
@@ -604,7 +641,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
// clang-format off // clang-format off
cmd("if")("CMKR_ROOT_PROJECT", "AND", "NOT", "CMKR_DISABLE_VCPKG"); cmd("if")("CMKR_ROOT_PROJECT", "AND", "NOT", "CMKR_DISABLE_VCPKG");
cmd("include")("FetchContent"); cmd("include")("FetchContent");
cmd("message")("STATUS", "Fetching vcpkg..."); cmd("message")("STATUS", "Fetching vcpkg (" + version_name + ")...");
cmd("FetchContent_Declare")("vcpkg", "URL", url); cmd("FetchContent_Declare")("vcpkg", "URL", url);
cmd("FetchContent_MakeAvailable")("vcpkg"); cmd("FetchContent_MakeAvailable")("vcpkg");
cmd("include")("${vcpkg_SOURCE_DIR}/scripts/buildsystems/vcpkg.cmake"); cmd("include")("${vcpkg_SOURCE_DIR}/scripts/buildsystems/vcpkg.cmake");
@@ -671,7 +708,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
ofs << " ],\n"; ofs << " ],\n";
ofs << " \"description\": \"" << escape(project.project_description) << "\",\n"; ofs << " \"description\": \"" << escape(project.project_description) << "\",\n";
ofs << " \"name\": \"" << escape(project.project_name) << "\",\n"; ofs << " \"name\": \"" << escape(vcpkg_escape_identifier(project.project_name)) << "\",\n";
ofs << R"( "version-string": "")" << '\n'; ofs << R"( "version-string": "")" << '\n';
ofs << "}\n"; ofs << "}\n";
} }
@@ -679,13 +716,23 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
if (!project.contents.empty()) { if (!project.contents.empty()) {
cmd("include")("FetchContent").endl(); cmd("include")("FetchContent").endl();
for (const auto &content : project.contents) { for (const auto &content : project.contents) {
cmd("message")("STATUS", "Fetching " + content.name + "..."); ConditionScope cs(gen, content.condition);
ss << "FetchContent_Declare(\n\t" << content.name << "\n";
for (const auto &arg : content.arguments) { gen.conditional_includes(content.include_before);
ss << "\t" << arg.first << "\n\t\t" << arg.second << "\n"; gen.conditional_cmake(content.cmake_before);
std::string version_info = "";
if (content.arguments.contains("GIT_TAG")) {
version_info = " (" + content.arguments.at("GIT_TAG") + ")";
} else if (content.arguments.contains("SVN_REVISION")) {
version_info = " (" + content.arguments.at("SVN_REVISION") + ")";
} }
ss << ")\n"; cmd("message")("STATUS", "Fetching " + content.name + version_info + "...");
cmd("FetchContent_Declare")(content.name, content.arguments);
cmd("FetchContent_MakeAvailable")(content.name).endl(); cmd("FetchContent_MakeAvailable")(content.name).endl();
gen.conditional_includes(content.include_after);
gen.conditional_cmake(content.cmake_after);
} }
} }
@@ -698,6 +745,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
auto required = dep.required ? "REQUIRED" : ""; auto required = dep.required ? "REQUIRED" : "";
auto config = dep.config ? "CONFIG" : ""; auto config = dep.config ? "CONFIG" : "";
auto components = std::make_pair("COMPONENTS", dep.components); auto components = std::make_pair("COMPONENTS", dep.components);
ConditionScope cs(gen, dep.condition);
cmd("find_package")(dep.name, version, required, config, components).endl(); cmd("find_package")(dep.name, version, required, config, components).endl();
} }
} }
@@ -727,52 +775,69 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
endl(); endl();
} }
for (const auto &subdir : project.subdirs) { for (const auto &subdir : project.subdirs) {
if (!subdir.condition.empty()) { ConditionScope cs(gen, subdir.condition);
const auto &condition = subdir.condition;
if (project.conditions.count(condition) == 0) {
throw std::runtime_error("Unknown condition '" + condition + "' for [subdir." + subdir.name + "]");
}
gen.cmd("if", condition)(RawArg(project.conditions[condition]));
}
gen.handle_condition(subdir.include_before, gen.conditional_includes(subdir.include_before);
[&](const std::string &, const std::vector<std::string> &includes) { inject_includes(includes); }); gen.conditional_cmake(subdir.cmake_before);
gen.handle_condition(subdir.cmake_before, [&](const std::string &, const std::string &cmake) { inject_cmake(cmake); });
add_subdir(subdir.name); add_subdir(subdir.name);
gen.handle_condition(subdir.include_after, [&](const std::string &, const std::vector<std::string> &includes) { inject_includes(includes); }); gen.conditional_includes(subdir.include_after);
gen.handle_condition(subdir.cmake_after, [&](const std::string &, const std::string &cmake) { inject_cmake(cmake); }); gen.conditional_cmake(subdir.cmake_after);
if (!subdir.condition.empty()) {
cmd("endif")();
}
} }
if (!project.targets.empty()) { if (!project.targets.empty()) {
for (const auto &target : project.targets) { for (size_t i = 0; i < project.targets.size(); i++) {
if (i > 0) {
endl();
}
const auto &target = project.targets[i];
const parser::Template *tmplate = nullptr;
std::unique_ptr<ConditionScope> tmplate_cs{};
comment("Target " + target.name); comment("Target " + target.name);
if (!target.condition.empty()) { // Check if this target is using a template.
const auto &condition = target.condition; if (target.type == parser::target_template) {
if (project.conditions.count(condition) == 0) { for (const auto &t : project.templates) {
throw std::runtime_error("Unknown condition '" + condition + "' for [target." + target.name + "]"); if (target.type_name == t.outline.name) {
tmplate = &t;
tmplate_cs = std::unique_ptr<ConditionScope>(new ConditionScope(gen, tmplate->outline.condition));
}
} }
gen.cmd("if", condition)(RawArg(project.conditions[condition]));
} }
ConditionScope cs(gen, target.condition);
cmd("set")("CMKR_TARGET", target.name); cmd("set")("CMKR_TARGET", target.name);
gen.handle_condition(target.include_before, if (tmplate != nullptr) {
[&](const std::string &, const std::vector<std::string> &includes) { inject_includes(includes); }); gen.conditional_includes(tmplate->outline.include_before);
gen.handle_condition(target.cmake_before, [&](const std::string &, const std::string &cmake) { inject_cmake(cmake); }); 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"; auto sources_var = target.name + "_SOURCES";
bool added_toml = false; bool added_toml = false;
cmd("set")(sources_var, RawArg("\"\"")).endl(); 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");
}
cmd("list")("APPEND", sources_var, sources);
});
}
gen.handle_condition(target.sources, [&](const std::string &condition, const std::vector<std::string> &condition_sources) { gen.handle_condition(target.sources, [&](const std::string &condition, const std::vector<std::string> &condition_sources) {
auto sources = expand_cmake_paths(condition_sources, path); auto sources = expand_cmake_paths(condition_sources, path, root_project);
if (sources.empty()) { if (sources.empty()) {
auto source_key = condition.empty() ? "sources" : (condition + ".sources"); auto source_key = condition.empty() ? "sources" : (condition + ".sources");
throw std::runtime_error(target.name + " " + source_key + " wildcard found 0 files"); throw std::runtime_error(target.name + " " + source_key + " wildcard found 0 files");
@@ -784,66 +849,91 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
cmd("list")("APPEND", sources_var, sources); cmd("list")("APPEND", sources_var, sources);
}); });
if (!added_toml && target.type != parser::target_interface) { auto target_type = target.type;
if (tmplate != nullptr) {
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("list")("APPEND", sources_var, std::vector<std::string>{"cmake.toml"}).endl();
} }
cmd("set")("CMKR_SOURCES", "${" + sources_var + "}"); cmd("set")("CMKR_SOURCES", "${" + sources_var + "}");
std::string add_command; std::string add_command;
std::string target_type; std::string target_type_string;
std::string target_scope; std::string target_scope;
switch (target.type) {
switch (target_type) {
case parser::target_executable: case parser::target_executable:
add_command = "add_executable"; add_command = "add_executable";
target_type = ""; target_type_string = "";
target_scope = "PRIVATE"; target_scope = "PRIVATE";
break; break;
case parser::target_library: case parser::target_library:
add_command = "add_library"; add_command = "add_library";
target_type = ""; target_type_string = "";
target_scope = "PUBLIC"; target_scope = "PUBLIC";
break; break;
case parser::target_shared: case parser::target_shared:
add_command = "add_library"; add_command = "add_library";
target_type = "SHARED"; target_type_string = "SHARED";
target_scope = "PUBLIC"; target_scope = "PUBLIC";
break; break;
case parser::target_static: case parser::target_static:
add_command = "add_library"; add_command = "add_library";
target_type = "STATIC"; target_type_string = "STATIC";
target_scope = "PUBLIC"; target_scope = "PUBLIC";
break; break;
case parser::target_interface: case parser::target_interface:
add_command = "add_library"; add_command = "add_library";
target_type = "INTERFACE"; target_type_string = "INTERFACE";
target_scope = "INTERFACE"; target_scope = "INTERFACE";
break; break;
case parser::target_custom: case parser::target_custom:
// TODO: add proper support, this is hacky // TODO: add proper support, this is hacky
add_command = "add_custom_target"; add_command = "add_custom_target";
target_type = "SOURCES"; target_type_string = "SOURCES";
target_scope = "PUBLIC"; target_scope = "PUBLIC";
break; break;
case parser::target_object: case parser::target_object:
// NOTE: This is properly supported since 3.12 // NOTE: This is properly supported since 3.12
add_command = "add_library"; add_command = "add_library";
target_type = "OBJECT"; target_type_string = "OBJECT";
target_scope = "PUBLIC"; target_scope = "PUBLIC";
break; break;
default: default:
throw std::runtime_error("Unimplemented enum value"); throw std::runtime_error("Unimplemented enum value");
} }
cmd(add_command)(target.name, target_type).endl(); // Handle custom add commands from templates.
if (tmplate != nullptr && !tmplate->add_function.empty()) {
add_command = tmplate->add_function;
target_type_string = ""; // TODO: let templates supply options to the add_command here?
// clang-format off if (tmplate->pass_sources_to_add_function) {
cmd("if")(sources_var); cmd(add_command)(target.name, target_type_string, "${" + sources_var + "}");
cmd("target_sources")(target.name, target.type == parser::target_interface ? "INTERFACE" : "PRIVATE", "${" + sources_var + "}"); } else {
cmd("endif")().endl(); cmd(add_command)(target.name, target_type_string).endl();
// clang-format on
// 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
}
} 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
}
// The first executable target will become the Visual Studio startup project // The first executable target will become the Visual Studio startup project
if (target.type == parser::target_executable) { if (target_type == parser::target_executable) {
cmd("get_directory_property")("CMKR_VS_STARTUP_PROJECT", "DIRECTORY", "${PROJECT_SOURCE_DIR}", "DEFINITION", "VS_STARTUP_PROJECT"); cmd("get_directory_property")("CMKR_VS_STARTUP_PROJECT", "DIRECTORY", "${PROJECT_SOURCE_DIR}", "DEFINITION", "VS_STARTUP_PROJECT");
// clang-format off // clang-format off
cmd("if")("NOT", "CMKR_VS_STARTUP_PROJECT"); cmd("if")("NOT", "CMKR_VS_STARTUP_PROJECT");
@@ -865,49 +955,79 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
[&](const std::string &, const std::vector<std::string> &args) { cmd(command)(target.name, scope, args); }); [&](const std::string &, const std::vector<std::string> &args) { cmd(command)(target.name, scope, args); });
}; };
target_cmd("target_compile_definitions", target.compile_definitions, target_scope); auto gen_target_cmds = [&](const parser::Target &t) {
target_cmd("target_compile_definitions", target.private_compile_definitions, "PRIVATE"); target_cmd("target_compile_definitions", t.compile_definitions, target_scope);
target_cmd("target_compile_definitions", t.private_compile_definitions, "PRIVATE");
target_cmd("target_compile_features", target.compile_features, target_scope); target_cmd("target_compile_features", t.compile_features, target_scope);
target_cmd("target_compile_features", target.private_compile_features, "PRIVATE"); target_cmd("target_compile_features", t.private_compile_features, "PRIVATE");
target_cmd("target_compile_options", target.compile_options, target_scope); target_cmd("target_compile_options", t.compile_options, target_scope);
target_cmd("target_compile_options", target.private_compile_options, "PRIVATE"); target_cmd("target_compile_options", t.private_compile_options, "PRIVATE");
target_cmd("target_include_directories", target.include_directories, target_scope); cmkr::parser::ConditionVector include_directories;
target_cmd("target_include_directories", target.private_include_directories, "PRIVATE"); for (const auto &itr : t.include_directories) {
auto directories = itr.second;
if (target_scope == "PUBLIC" || target_scope == "INTERFACE") {
for (auto &directory : directories) {
// TODO: properly detect if this is a relative path
if (directory[0] != '$') {
directory = "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/" + directory + ">";
}
}
}
include_directories[itr.first] = directories;
}
// TODO: this needs to be done cleaner, maybe the install command has some magic?
include_directories[""].push_back("$<INSTALL_INTERFACE:include>");
target_cmd("target_link_directories", target.link_directories, target_scope); target_cmd("target_include_directories", include_directories, target_scope);
target_cmd("target_link_directories", target.private_link_directories, "PRIVATE"); target_cmd("target_include_directories", t.private_include_directories, "PRIVATE");
target_cmd("target_link_libraries", target.link_libraries, target_scope); target_cmd("target_link_directories", t.link_directories, target_scope);
target_cmd("target_link_libraries", target.private_link_libraries, "PRIVATE"); target_cmd("target_link_directories", t.private_link_directories, "PRIVATE");
target_cmd("target_link_options", target.link_options, target_scope); target_cmd("target_link_libraries", t.link_libraries, target_scope);
target_cmd("target_link_options", target.private_link_options, "PRIVATE"); target_cmd("target_link_libraries", t.private_link_libraries, "PRIVATE");
target_cmd("target_precompile_headers", target.precompile_headers, target_scope); target_cmd("target_link_options", t.link_options, target_scope);
target_cmd("target_precompile_headers", target.private_precompile_headers, "PRIVATE"); target_cmd("target_link_options", t.private_link_options, "PRIVATE");
if (!target.properties.empty()) { target_cmd("target_precompile_headers", t.precompile_headers, target_scope);
gen.handle_condition(target.properties, [&](const std::string &, const tsl::ordered_map<std::string, std::string> &properties) { target_cmd("target_precompile_headers", t.private_precompile_headers, "PRIVATE");
};
if (tmplate != nullptr) {
gen_target_cmds(tmplate->outline);
}
gen_target_cmds(target);
if (!target.properties.empty() || (tmplate != nullptr && !tmplate->outline.properties.empty())) {
auto props = target.properties;
if (tmplate != nullptr) {
props.insert(tmplate->outline.properties.begin(), tmplate->outline.properties.end());
}
gen.handle_condition(props, [&](const std::string &, const tsl::ordered_map<std::string, std::string> &properties) {
cmd("set_target_properties")(target.name, "PROPERTIES", properties); cmd("set_target_properties")(target.name, "PROPERTIES", properties);
}); });
} }
gen.handle_condition(target.include_after, gen.conditional_includes(target.include_after);
[&](const std::string &, const std::vector<std::string> &includes) { inject_includes(includes); }); gen.conditional_cmake(target.cmake_after);
gen.handle_condition(target.cmake_after, [&](const std::string &, const std::string &cmake) { inject_cmake(cmake); });
if (tmplate != nullptr) {
gen.conditional_includes(tmplate->outline.include_after);
gen.conditional_cmake(tmplate->outline.cmake_after);
}
cmd("unset")("CMKR_TARGET"); cmd("unset")("CMKR_TARGET");
cmd("unset")("CMKR_SOURCES"); cmd("unset")("CMKR_SOURCES");
if (!target.condition.empty()) {
cmd("endif")();
}
endl();
} }
endl();
} }
if (!project.tests.empty()) { if (!project.tests.empty()) {
@@ -922,6 +1042,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
auto working_directory = std::make_pair("WORKING_DIRECTORY", dir); auto working_directory = std::make_pair("WORKING_DIRECTORY", dir);
auto command = std::make_pair("COMMAND", test.command); auto command = std::make_pair("COMMAND", test.command);
auto arguments = std::make_pair("", test.arguments); auto arguments = std::make_pair("", test.arguments);
ConditionScope cs(gen, test.condition);
cmd("add_test")(name, configurations, working_directory, command, arguments).endl(); cmd("add_test")(name, configurations, working_directory, command, arguments).endl();
} }
} }
@@ -932,7 +1053,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
auto dirs = std::make_pair("DIRS", inst.dirs); auto dirs = std::make_pair("DIRS", inst.dirs);
std::vector<std::string> files_data; std::vector<std::string> files_data;
if (!inst.files.empty()) { if (!inst.files.empty()) {
files_data = expand_cmake_paths(inst.files, path); files_data = expand_cmake_paths(inst.files, path, root_project);
if (files_data.empty()) { if (files_data.empty()) {
throw std::runtime_error("[[install]] files wildcard did not resolve to any files"); throw std::runtime_error("[[install]] files wildcard did not resolve to any files");
} }
@@ -940,11 +1061,94 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
auto files = std::make_pair("FILES", inst.files); auto files = std::make_pair("FILES", inst.files);
auto configs = std::make_pair("CONFIGURATIONS", inst.configs); auto configs = std::make_pair("CONFIGURATIONS", inst.configs);
auto destination = std::make_pair("DESTINATION", inst.destination); auto destination = std::make_pair("DESTINATION", inst.destination);
auto component = std::make_pair("COMPONENT", inst.targets.empty() ? "" : inst.targets.front()); auto component_name = inst.component;
if (component_name.empty() && !inst.targets.empty()) {
component_name = inst.targets.front();
}
auto component = std::make_pair("COMPONENT", component_name);
ConditionScope cs(gen, inst.condition);
cmd("install")(targets, dirs, files, configs, destination, component); cmd("install")(targets, dirs, files, configs, destination, component);
} }
} }
if (!project.package.targets.empty()) {
//TODO: generate an option to enable/disable installation
const auto &package = project.package;
if (!root_project) {
throw std::runtime_error("[package] has to be in the project root");
}
comment("Project packaging");
cmd("include")("CMakePackageConfigHelpers").endl();
comment("Install headers");
tsl::ordered_map<std::string, std::vector<std::string>> header_tree;
for (const auto &pattern : package.headers) {
auto pattern_path = fs::path(pattern);
auto pattern_parent = pattern_path.parent_path();
auto headers = expand_cmake_path(pattern_path, path, root_project);
for (const auto &header : headers) {
auto install_path = extract_suffix(pattern_parent, header);
auto install_destination = "include/" + fs::path(install_path).parent_path().string();
header_tree[install_destination].push_back(header);
}
}
for (const auto &itr : header_tree) {
cmd("install")(std::make_pair("FILES", itr.second), std::make_pair("DESTINATION", itr.first));
printf("Install destination: %s\n", itr.first.c_str());
for (const auto &header : itr.second) {
printf(" Header: %s\n", header.c_str());
}
}
endl();
comment("Install target outputs and generate target exports");
auto export_name = package.name + "Targets";
cmd("install")(std::make_pair("TARGETS", package.targets), std::make_pair("EXPORT", export_name), "LIBRARY", "ARCHIVE", "RUNTIME");
endl();
comment("Install generated target exports");
auto cmake_destination = "lib/cmake/" + package.name;
cmd("install")(std::make_pair("EXPORT", export_name), std::make_pair("FILE", export_name + ".cmake"),
std::make_pair("NAMESPACE", package.namespace_name), std::make_pair("DESTINATION", cmake_destination));
endl();
auto config_base = "${CMAKE_CURRENT_BINARY_DIR}/" + package.name;
std::vector<std::string> generated_scripts;
if (!project.project_version.empty()) {
comment("Generate package version scripts");
auto version_cmake = config_base + "ConfigVersion.cmake";
cmd("write_basic_package_version_file")(version_cmake, std::make_pair("VERSION", "${PROJECT_VERSION}"),
std::make_pair("COMPATIBILITY", "SameMajorVersion"));
endl();
generated_scripts.push_back(version_cmake);
}
comment("Generate package config scripts");
cmd("set")("PACKAGE_NAME", package.name);
// TODO: this needs some actual logic
cmd("set")("PACKAGE_DEPENDENCIES", package.dependencies);
auto config_cmake = config_base + "Config.cmake";
auto config_in = config_cmake + ".in";
cmd("file")("WRITE", config_in, RawArg(R"("include(CMakeFindDependencyMacro)\n\n")"));
cmd("file")("APPEND", config_in, RawArg(R"("string(REGEX MATCHALL \"[^;]+\" SEPARATE_DEPENDENCIES \"@PACKAGE_DEPENDENCIES@\")\n")"));
cmd("file")("APPEND", config_in, RawArg(R"("foreach(dependency ${SEPARATE_DEPENDENCIES})\n")"));
cmd("file")("APPEND", config_in, RawArg(R"(" string(REPLACE \" \" \";\" args \"\${dependency}\")\n")"));
cmd("file")("APPEND", config_in, RawArg(R"(" find_dependency(\${args})\n")"));
cmd("file")("APPEND", config_in, RawArg(R"("endforeach()\n\n")"));
cmd("file")("APPEND", config_in, RawArg(R"("include(\"\${CMAKE_CURRENT_LIST_DIR}/@PACKAGE_NAME@Targets.cmake\")\n")"));
cmd("configure_file")(config_in, config_cmake, "@ONLY");
generated_scripts.push_back(config_cmake);
endl();
comment("Install generated package scripts");
cmd("install")(std::make_pair("FILES", generated_scripts), std::make_pair("DESTINATION", cmake_destination));
endl();
}
// Generate CMakeLists.txt // Generate CMakeLists.txt
auto list_path = fs::path(path) / "CMakeLists.txt"; auto list_path = fs::path(path) / "CMakeLists.txt";
+281 -125
View File
@@ -1,39 +1,28 @@
#include "project_parser.hpp" #include "project_parser.hpp"
#include "enum_helper.hpp"
#include "fs.hpp" #include "fs.hpp"
#include <deque> #include <deque>
#include <stdexcept> #include <stdexcept>
#include <toml.hpp> #include <toml.hpp>
#include <tsl/ordered_map.h> #include <tsl/ordered_map.h>
#include <tsl/ordered_set.h>
template <>
const char *enumStrings<cmkr::parser::TargetType>::data[] = {"executable", "library", "shared", "static", "interface", "custom", "object"};
namespace cmkr { namespace cmkr {
namespace parser { namespace parser {
using TomlBasicValue = toml::basic_value<toml::discard_comments, tsl::ordered_map, std::vector>; const char *targetTypeNames[target_last] = {"executable", "library", "shared", "static", "interface", "custom", "object", "template"};
template <typename EnumType> static TargetType parse_targetType(const std::string &name) {
static EnumType to_enum(const std::string &str, const std::string &help_name) { for (int i = 0; i < target_last; i++) {
EnumType value; if (name == targetTypeNames[i]) {
try { return static_cast<TargetType>(i);
std::stringstream ss(str);
ss >> enumFromString(value);
} catch (std::invalid_argument &) {
std::string supported;
for (const auto &s : enumStrings<EnumType>::data) {
if (!supported.empty()) {
supported += ", ";
}
supported += s;
} }
throw std::runtime_error("Unknown " + help_name + "'" + str + "'! Supported types are: " + supported);
} }
return value; return target_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_error(const std::string &error, const toml::key &ky, const TomlBasicValue &value) {
auto loc = value.location(); auto loc = value.location();
auto line_number_str = std::to_string(loc.line()); auto line_number_str = std::to_string(loc.line());
@@ -42,7 +31,7 @@ static std::string format_key_error(const std::string &error, const toml::key &k
std::ostringstream oss; std::ostringstream oss;
oss << "[error] " << error << '\n'; oss << "[error] " << error << '\n';
oss << " --> " << loc.file_name() << '\n'; oss << " --> " << loc.file_name() << ':' << loc.line() << '\n';
oss << std::string(line_width + 2, ' ') << "|\n"; oss << std::string(line_width + 2, ' ') << "|\n";
oss << ' ' << line_number_str << " | " << line_str << '\n'; oss << ' ' << line_number_str << " | " << line_str << '\n';
@@ -55,15 +44,14 @@ static std::string format_key_error(const std::string &error, const toml::key &k
if (key_start != std::string::npos) { if (key_start != std::string::npos) {
oss << std::string(key_start + 1, ' ') << std::string(ky.length(), '~'); oss << std::string(key_start + 1, ' ') << std::string(ky.length(), '~');
} }
oss << '\n';
return oss.str(); return oss.str();
} }
class TomlChecker { class TomlChecker {
const TomlBasicValue &m_v; const TomlBasicValue &m_v;
tsl::ordered_map<toml::key, bool> m_visited; tsl::ordered_set<toml::key> m_visited;
tsl::ordered_map<toml::key, bool> m_conditionVisited; tsl::ordered_set<toml::key> m_conditionVisited;
public: public:
TomlChecker(const TomlBasicValue &v, const toml::key &ky) : m_v(toml::find(v, ky)) {} TomlChecker(const TomlBasicValue &v, const toml::key &ky) : m_v(toml::find(v, ky)) {}
@@ -72,7 +60,8 @@ class TomlChecker {
TomlChecker(TomlChecker &&) = delete; TomlChecker(TomlChecker &&) = delete;
template <typename T> template <typename T>
void optional(const toml::key &ky, Condition<T> &destination) { bool optional(const toml::key &ky, Condition<T> &destination) {
auto found = false;
// TODO: this algorithm in O(n) over the amount of keys, kinda bad // TODO: this algorithm in O(n) over the amount of keys, kinda bad
const auto &table = m_v.as_table(); const auto &table = m_v.as_table();
for (const auto &itr : table) { for (const auto &itr : table) {
@@ -81,28 +70,34 @@ class TomlChecker {
if (value.is_table()) { if (value.is_table()) {
if (value.contains(ky)) { if (value.contains(ky)) {
destination[key] = toml::find<T>(value, ky); destination[key] = toml::find<T>(value, ky);
found = true;
} }
} else if (key == ky) { } else if (key == ky) {
destination[""] = toml::find<T>(m_v, ky); destination[""] = toml::find<T>(m_v, ky);
found = true;
} }
} }
// Handle visiting logic // Handle visiting logic
for (const auto &itr : destination) { for (const auto &itr : destination) {
if (!itr.first.empty()) { if (!itr.first.empty()) {
m_conditionVisited.emplace(itr.first, true); m_conditionVisited.emplace(itr.first);
} }
} }
visit(ky); visit(ky);
return found;
} }
template <typename T> template <typename T>
void optional(const toml::key &ky, T &destination) { bool optional(const toml::key &ky, T &destination) {
auto found = false;
// TODO: this currently doesn't allow you to get an optional map<string, X> // TODO: this currently doesn't allow you to get an optional map<string, X>
if (m_v.contains(ky)) { if (m_v.contains(ky)) {
destination = toml::find<T>(m_v, ky); destination = toml::find<T>(m_v, ky);
found = true;
} }
visit(ky); visit(ky);
return found;
} }
template <typename T> template <typename T>
@@ -121,7 +116,9 @@ class TomlChecker {
return toml::find(m_v, ky); return toml::find(m_v, ky);
} }
void visit(const toml::key &ky) { m_visited.emplace(ky, true); } void visit(const toml::key &ky) { m_visited.emplace(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 { void check(const tsl::ordered_map<std::string, std::string> &conditions) const {
for (const auto &itr : m_v.as_table()) { for (const auto &itr : m_v.as_table()) {
@@ -145,20 +142,32 @@ class TomlChecker {
} }
} }
throw std::runtime_error(format_key_error("Unknown key '" + ky + "'", ky, itr.second)); throw std::runtime_error(format_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));
}
} }
} }
} }
}; };
class TomlCheckerRoot { class TomlCheckerRoot {
const TomlBasicValue &m_root;
std::deque<TomlChecker> m_checkers; std::deque<TomlChecker> m_checkers;
tsl::ordered_set<toml::key> m_visisted;
bool m_checked = false; bool m_checked = false;
public: public:
TomlCheckerRoot() = default; TomlCheckerRoot(const TomlBasicValue &root) : m_root(root) {}
TomlCheckerRoot(const TomlCheckerRoot &) = delete; TomlCheckerRoot(const TomlCheckerRoot &) = delete;
TomlCheckerRoot(TomlCheckerRoot &&) = delete; TomlCheckerRoot(TomlCheckerRoot &&) = delete;
bool contains(const toml::key &ky) {
m_visisted.emplace(ky);
return m_root.contains(ky);
}
TomlChecker &create(const TomlBasicValue &v) { TomlChecker &create(const TomlBasicValue &v) {
m_checkers.emplace_back(v); m_checkers.emplace_back(v);
return m_checkers.back(); return m_checkers.back();
@@ -169,7 +178,14 @@ class TomlCheckerRoot {
return m_checkers.back(); return m_checkers.back();
} }
void check(const tsl::ordered_map<std::string, std::string> &conditions) { void check(const tsl::ordered_map<std::string, std::string> &conditions, bool check_root) {
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));
}
}
}
for (const auto &checker : m_checkers) { for (const auto &checker : m_checkers) {
checker.check(conditions); checker.check(conditions);
} }
@@ -179,13 +195,16 @@ class TomlCheckerRoot {
Project::Project(const Project *parent, const std::string &path, bool build) { Project::Project(const Project *parent, const std::string &path, bool build) {
const auto toml_path = fs::path(path) / "cmake.toml"; const auto toml_path = fs::path(path) / "cmake.toml";
if (!fs::exists(toml_path)) { if (!fs::exists(toml_path)) {
throw std::runtime_error("No cmake.toml was found!"); throw std::runtime_error("File not found '" + toml_path.string() + "'");
} }
const auto toml = toml::parse<toml::discard_comments, tsl::ordered_map, std::vector>(toml_path.string()); const auto toml = toml::parse<toml::discard_comments, tsl::ordered_map, std::vector>(toml_path.string());
if (toml.size() == 0) {
throw std::runtime_error("Empty TOML '" + toml_path.string() + "'");
}
TomlCheckerRoot checker; TomlCheckerRoot checker(toml);
if (toml.contains("cmake")) { if (checker.contains("cmake")) {
auto &cmake = checker.create(toml, "cmake"); auto &cmake = checker.create(toml, "cmake");
cmake.required("version", cmake_version); cmake.required("version", cmake_version);
@@ -217,7 +236,7 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
// Skip the rest of the parsing when building // Skip the rest of the parsing when building
if (build) { if (build) {
checker.check(conditions); checker.check(conditions, false);
return; return;
} }
@@ -231,18 +250,22 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
conditions["gcc"] = R"cmake(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "GNU")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" OR CMAKE_C_COMPILER_ID MATCHES "Clang")cmake";
conditions["msvc"] = R"cmake(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";
conditions["x32"] = R"cmake(CMAKE_SIZEOF_VOID_P EQUAL 4)cmake";
} else { } else {
conditions = parent->conditions; conditions = parent->conditions;
templates = parent->templates;
} }
if (toml.contains("conditions")) { if (checker.contains("conditions")) {
auto conds = toml::find<decltype(conditions)>(toml, "conditions"); auto conds = toml::find<decltype(conditions)>(toml, "conditions");
for (const auto &cond : conds) { for (const auto &cond : conds) {
conditions[cond.first] = cond.second; conditions[cond.first] = cond.second;
} }
} }
if (toml.contains("project")) { if (checker.contains("project")) {
auto &project = checker.create(toml, "project"); auto &project = checker.create(toml, "project");
project.required("name", project_name); project.required("name", project_name);
project.optional("version", project_version); project.optional("version", project_version);
@@ -255,7 +278,7 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
project.optional("subdirs", project_subdirs); project.optional("subdirs", project_subdirs);
} }
if (toml.contains("subdir")) { if (checker.contains("subdir")) {
const auto &subs = toml::find(toml, "subdir").as_table(); const auto &subs = toml::find(toml, "subdir").as_table();
for (const auto &itr : subs) { for (const auto &itr : subs) {
Subdir subdir; Subdir subdir;
@@ -272,7 +295,7 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
} }
} }
if (toml.contains("settings")) { if (checker.contains("settings")) {
using set_map = tsl::ordered_map<std::string, TomlBasicValue>; using set_map = tsl::ordered_map<std::string, TomlBasicValue>;
const auto &sets = toml::find<set_map>(toml, "settings"); const auto &sets = toml::find<set_map>(toml, "settings");
for (const auto &itr : sets) { for (const auto &itr : sets) {
@@ -301,7 +324,7 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
} }
} }
if (toml.contains("options")) { if (checker.contains("options")) {
using opts_map = tsl::ordered_map<std::string, TomlBasicValue>; using opts_map = tsl::ordered_map<std::string, TomlBasicValue>;
const auto &opts = toml::find<opts_map>(toml, "options"); const auto &opts = toml::find<opts_map>(toml, "options");
for (const auto &itr : opts) { for (const auto &itr : opts) {
@@ -319,17 +342,18 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
} }
} }
if (toml.contains("find-package")) { if (checker.contains("find-package")) {
using pkg_map = tsl::ordered_map<std::string, TomlBasicValue>; using pkg_map = tsl::ordered_map<std::string, TomlBasicValue>;
const auto &pkgs = toml::find<pkg_map>(toml, "find-package"); const auto &pkgs = toml::find<pkg_map>(toml, "find-package");
for (const auto &itr : pkgs) { for (const auto &itr : pkgs) {
Package p; FindPackage p;
p.name = itr.first; p.name = itr.first;
const auto &value = itr.second; const auto &value = itr.second;
if (itr.second.is_string()) { if (itr.second.is_string()) {
p.version = itr.second.as_string(); p.version = itr.second.as_string();
} else { } else {
auto &pkg = checker.create(value); auto &pkg = checker.create(value);
pkg.optional("condition", p.condition);
pkg.optional("version", p.version); pkg.optional("version", p.version);
pkg.optional("required", p.required); pkg.optional("required", p.required);
pkg.optional("config", p.config); pkg.optional("config", p.config);
@@ -339,136 +363,249 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
} }
} }
// TODO: perform checking here if (checker.contains("fetch-content")) {
if (toml.contains("fetch-content")) {
const auto &fc = toml::find(toml, "fetch-content").as_table(); const auto &fc = toml::find(toml, "fetch-content").as_table();
for (const auto &itr : fc) { for (const auto &itr : fc) {
Content content; Content content;
content.name = itr.first; content.name = itr.first;
auto &c = checker.create(itr.second);
c.optional("condition", content.condition);
c.optional("cmake-before", content.cmake_before);
c.optional("cmake-after", content.cmake_after);
c.optional("include-before", content.include_before);
c.optional("include-after", content.include_after);
for (const auto &argItr : itr.second.as_table()) { for (const auto &argItr : itr.second.as_table()) {
std::string value;
if (argItr.second.is_array()) {
for (const auto &list_val : argItr.second.as_array()) {
if (!value.empty()) {
value += ';';
}
value += list_val.as_string();
}
} else if (argItr.second.is_boolean()) {
value = argItr.second.as_boolean() ? "ON" : "OFF";
} else {
value = argItr.second.as_string();
}
auto is_cmake_arg = [](const std::string &s) {
for (auto c : s) {
if (!(std::isdigit(c) || std::isupper(c) || c == '_')) {
return false;
}
}
return true;
};
// https://cmake.org/cmake/help/latest/command/string.html#supported-hash-algorithms
tsl::ordered_set<std::string> hash_algorithms = {
"md5", "sha1", "sha224", "sha256", "sha384", "sha512", "sha3_224", "sha3_256", "sha3_384", "sha3_512",
};
auto key = argItr.first; auto key = argItr.first;
if (key == "git") { if (key == "git") {
key = "GIT_REPOSITORY"; key = "GIT_REPOSITORY";
} else if (key == "tag") { } else if (key == "tag") {
key = "GIT_TAG"; key = "GIT_TAG";
} else if (key == "shallow") {
key = "GIT_SHALLOW";
} else if (key == "svn") { } else if (key == "svn") {
key = "SVN_REPOSITORY"; key = "SVN_REPOSITORY";
} else if (key == "rev") { } else if (key == "rev") {
key = "SVN_REVISION"; key = "SVN_REVISION";
} else if (key == "url") { } else if (key == "url") {
key = "URL"; key = "URL";
} else if (hash_algorithms.contains(key)) {
std::string algo;
for (auto c : key) {
algo.push_back(std::toupper(c));
}
key = "URL_HASH";
value = algo + "=" + value;
} else if (key == "hash") { } else if (key == "hash") {
key = "URL_HASH"; key = "URL_HASH";
} else { } else if (is_cmake_arg(key)) {
// don't change arg // 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));
} }
content.arguments.emplace(key, argItr.second.as_string());
// Make sure not to emit keys like "condition" in the FetchContent call
if (!c.visisted(key)) {
content.arguments.emplace(key, value);
}
c.visit(argItr.first);
} }
contents.emplace_back(std::move(content)); contents.emplace_back(std::move(content));
} }
} }
if (toml.contains("bin")) { if (checker.contains("bin")) {
throw std::runtime_error("[[bin]] has been renamed to [[target]]"); throw std::runtime_error("[[bin]] has been renamed to [[target]]");
} }
if (toml.contains("target")) { auto parse_target = [&](const std::string &name, TomlChecker &t, bool isTemplate) {
const auto &ts = toml::find(toml, "target").as_table(); Target target;
target.name = name;
for (const auto &itr : ts) { t.required("type", target.type_name);
const auto &value = itr.second; target.type = parse_targetType(target.type_name);
Target target; // Users cannot set this target type
target.name = itr.first; if (target.type == target_template) {
target.type = target_last;
}
auto &t = checker.create(value); if (!isTemplate && target.type == target_last) {
std::string type; for (const auto &tmplate : templates) {
t.required("type", type); if (target.type_name == tmplate.outline.name) {
target.type = to_enum<TargetType>(type, "target type"); target.type = target_template;
break;
t.optional("headers", target.headers); }
t.optional("sources", target.sources);
t.optional("compile-definitions", target.compile_definitions);
t.optional("private-compile-definitions", target.private_compile_definitions);
t.optional("compile-features", target.compile_features);
t.optional("private-compile-features", target.private_compile_features);
t.optional("compile-options", target.compile_options);
t.optional("private-compile-options", target.private_compile_options);
t.optional("include-directories", target.include_directories);
t.optional("private-include-directories", target.private_include_directories);
t.optional("link-directories", target.link_directories);
t.optional("private-link-directories", target.private_link_directories);
t.optional("link-libraries", target.link_libraries);
t.optional("private-link-libraries", target.private_link_libraries);
t.optional("link-options", target.link_options);
t.optional("private-link-options", target.private_link_options);
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());
} }
}
t.optional("condition", target.condition); if (target.type == target_last) {
t.optional("alias", target.alias); std::string error = "Unknown target type '" + target.type_name + "'\n";
error += "Available types:\n";
for (std::string type_name : targetTypeNames) {
if (type_name != "template") {
error += " - " + type_name + "\n";
}
}
if (!isTemplate && !templates.empty()) {
error += "Available templates:\n";
for (const auto &tmplate : templates) {
error += " - " + tmplate.outline.name + "\n";
}
}
error.pop_back(); // Remove last newline
throw std::runtime_error(format_key_error(error, target.type_name, t.find("type")));
}
if (t.contains("properties")) { t.optional("headers", target.headers);
auto store_property = [&target](const toml::key &k, const TomlBasicValue &v, const std::string &condition) { t.optional("sources", target.sources);
if (v.is_array()) {
std::string property_list; t.optional("compile-definitions", target.compile_definitions);
for (const auto &list_val : v.as_array()) { t.optional("private-compile-definitions", target.private_compile_definitions);
if (!property_list.empty()) {
property_list += ';'; t.optional("compile-features", target.compile_features);
} t.optional("private-compile-features", target.private_compile_features);
property_list += list_val.as_string();
t.optional("compile-options", target.compile_options);
t.optional("private-compile-options", target.private_compile_options);
t.optional("include-directories", target.include_directories);
t.optional("private-include-directories", target.private_include_directories);
t.optional("link-directories", target.link_directories);
t.optional("private-link-directories", target.private_link_directories);
t.optional("link-libraries", target.link_libraries);
t.optional("private-link-libraries", target.private_link_libraries);
t.optional("link-options", target.link_options);
t.optional("private-link-options", target.private_link_options);
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());
}
t.optional("condition", target.condition);
t.optional("alias", target.alias);
if (t.contains("properties")) {
auto store_property = [&target](const toml::key &k, const TomlBasicValue &v, const std::string &condition) {
if (v.is_array()) {
std::string property_list;
for (const auto &list_val : v.as_array()) {
if (!property_list.empty()) {
property_list += ';';
} }
target.properties[condition][k] = property_list; property_list += list_val.as_string();
} else if (v.is_boolean()) {
target.properties[condition][k] = v.as_boolean() ? "ON" : "OFF";
} else {
target.properties[condition][k] = v.as_string();
} }
}; target.properties[condition][k] = property_list;
} else if (v.is_boolean()) {
target.properties[condition][k] = v.as_boolean() ? "ON" : "OFF";
} else {
target.properties[condition][k] = v.as_string();
}
};
const auto &props = t.find("properties").as_table(); const auto &props = t.find("properties").as_table();
for (const auto &propKv : props) { for (const auto &propKv : props) {
const auto &k = propKv.first; const auto &k = propKv.first;
const auto &v = propKv.second; const auto &v = propKv.second;
if (v.is_table()) { if (v.is_table()) {
for (const auto &condKv : v.as_table()) { for (const auto &condKv : v.as_table()) {
store_property(condKv.first, condKv.second, k); store_property(condKv.first, condKv.second, k);
}
} else {
store_property(k, v, "");
} }
} else {
store_property(k, v, "");
}
}
}
t.optional("cmake-before", target.cmake_before);
t.optional("cmake-after", target.cmake_after);
t.optional("include-before", target.include_before);
t.optional("include-after", target.include_after);
return target;
};
if (checker.contains("template")) {
const auto &ts = toml::find(toml, "template").as_table();
for (const auto &itr : ts) {
auto &t = checker.create(itr.second);
const auto &name = itr.first;
for (const auto &type_name : targetTypeNames) {
if (name == type_name) {
throw std::runtime_error(format_key_error("Reserved template name '" + name + "'", name, itr.second));
} }
} }
t.optional("cmake-before", target.cmake_before); for (const auto &tmplate : templates) {
t.optional("cmake-after", target.cmake_after); if (name == tmplate.outline.name) {
t.optional("include-before", target.include_before); throw std::runtime_error(format_key_error("Template '" + name + "' already defined", name, itr.second));
t.optional("include-after", target.include_after); }
}
targets.push_back(target); Template tmplate;
tmplate.outline = parse_target(name, t, true);
t.optional("add-function", tmplate.add_function);
t.optional("pass-sources-to-add-function", tmplate.pass_sources_to_add_function);
templates.push_back(tmplate);
} }
} }
if (toml.contains("test")) { if (checker.contains("target")) {
const auto &ts = toml::find(toml, "target").as_table();
for (const auto &itr : ts) {
auto &t = checker.create(itr.second);
targets.push_back(parse_target(itr.first, t, false));
}
}
if (checker.contains("test")) {
const auto &ts = toml::find(toml, "test").as_array(); const auto &ts = toml::find(toml, "test").as_array();
for (const auto &value : ts) { for (const auto &value : ts) {
auto &t = checker.create(value); auto &t = checker.create(value);
Test test; Test test;
t.required("name", test.name); t.required("name", test.name);
t.optional("condition", test.condition);
t.optional("configurations", test.configurations); t.optional("configurations", test.configurations);
t.optional("working-directory", test.working_directory); t.optional("working-directory", test.working_directory);
t.required("command", test.command); t.required("command", test.command);
@@ -477,21 +614,23 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
} }
} }
if (toml.contains("install")) { if (checker.contains("install")) {
const auto &is = toml::find(toml, "install").as_array(); const auto &is = toml::find(toml, "install").as_array();
for (const auto &value : is) { for (const auto &value : is) {
auto &i = checker.create(value); auto &i = checker.create(value);
Install inst; Install inst;
i.optional("condition", inst.condition);
i.optional("targets", inst.targets); i.optional("targets", inst.targets);
i.optional("files", inst.files); i.optional("files", inst.files);
i.optional("dirs", inst.dirs); i.optional("dirs", inst.dirs);
i.optional("configs", inst.configs); i.optional("configs", inst.configs);
i.required("destination", inst.destination); i.required("destination", inst.destination);
i.optional("component", inst.component);
installs.push_back(inst); installs.push_back(inst);
} }
} }
if (toml.contains("vcpkg")) { if (checker.contains("vcpkg")) {
auto &v = checker.create(toml, "vcpkg"); auto &v = checker.create(toml, "vcpkg");
v.optional("url", vcpkg.url); v.optional("url", vcpkg.url);
v.optional("version", vcpkg.version); v.optional("version", vcpkg.version);
@@ -517,7 +656,24 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
} }
} }
checker.check(conditions); if(checker.contains("package")) {
auto& p = checker.create(toml, "package");
if(!p.optional("name", package.name)) {
package.name = project_name;
}
p.required("targets", package.targets);
// TODO: should this be optional?
p.required("headers", package.headers);
// TODO: how to detect this should default to all dependencies vs no dependencies?
p.optional("dependencies", package.dependencies);
if(!p.optional("namespace", package.namespace_name)) {
package.namespace_name = project_name + "::";
} else {
// TODO: check if the namespace ends with "::"?
}
}
checker.check(conditions, true);
} }
bool is_root_path(const std::string &path) { bool is_root_path(const std::string &path) {
+10
View File
@@ -78,3 +78,13 @@ add_test(
build build
) )
add_test(
NAME
templates
WORKING_DIRECTORY
"${CMAKE_CURRENT_LIST_DIR}/templates"
COMMAND
$<TARGET_FILE:cmkr>
build
)
+19 -7
View File
@@ -1,41 +1,53 @@
[[test]] [[test]]
name = "basic" name = "basic"
command = "$<TARGET_FILE:cmkr>"
working-directory = "basic" working-directory = "basic"
command = "$<TARGET_FILE:cmkr>"
arguments = ["build"] arguments = ["build"]
[[test]] [[test]]
name = "interface" name = "interface"
command = "$<TARGET_FILE:cmkr>"
working-directory = "interface" working-directory = "interface"
command = "$<TARGET_FILE:cmkr>"
arguments = ["build"] arguments = ["build"]
[[test]] [[test]]
name = "fetch-content" name = "fetch-content"
command = "$<TARGET_FILE:cmkr>"
working-directory = "fetch-content" working-directory = "fetch-content"
command = "$<TARGET_FILE:cmkr>"
arguments = ["build"] arguments = ["build"]
[[test]] [[test]]
name = "conditions" name = "conditions"
command = "$<TARGET_FILE:cmkr>"
working-directory = "conditions" working-directory = "conditions"
command = "$<TARGET_FILE:cmkr>"
arguments = ["build"] arguments = ["build"]
[[test]] [[test]]
name = "vcpkg" name = "vcpkg"
command = "$<TARGET_FILE:cmkr>"
working-directory = "vcpkg" working-directory = "vcpkg"
command = "$<TARGET_FILE:cmkr>"
arguments = ["build"] arguments = ["build"]
[[test]] [[test]]
name = "cxx-standard" name = "cxx-standard"
command = "$<TARGET_FILE:cmkr>"
working-directory = "cxx-standard" working-directory = "cxx-standard"
command = "$<TARGET_FILE:cmkr>"
arguments = ["build"] arguments = ["build"]
[[test]] [[test]]
name = "globbing" name = "globbing"
command = "$<TARGET_FILE:cmkr>"
working-directory = "globbing" working-directory = "globbing"
command = "$<TARGET_FILE:cmkr>"
arguments = ["build"] arguments = ["build"]
[[test]]
name = "templates"
working-directory = "templates"
command = "$<TARGET_FILE:cmkr>"
arguments = ["build"]
[[test]]
name = "package"
working-directory = "package"
command = "$<TARGET_FILE:cmkr>"
arguments = ["build"]
+1
View File
@@ -5,6 +5,7 @@ description = "Header-only library"
[target.mylib] [target.mylib]
type = "interface" type = "interface"
include-directories = ["include"] include-directories = ["include"]
compile-features = ["cxx_std_11"]
[target.example] [target.example]
type = "executable" type = "executable"
+20
View File
@@ -0,0 +1,20 @@
[project]
name = "Utility"
version = "1.2"
[target.Utility]
type = "static"
alias = "Utility::Utility" # autogenerate this alias as ${PROJECT_NAME}::TargetName
sources = [
"src/Utility/*.cpp",
"include/Utility/*.hpp",
]
include-directories = ["include"]
compile-features = ["cxx_std_11"]
[package]
name = "Utility" # defaults to ${PROJECT_NAME}
targets = ["Utility"] # required
headers = ["include/**.hpp", "include/**.h"] # required
dependencies = [] # defaults to all the required packages
namespace = "Utility::" # defaults to ${PROJECT_NAME}::
+9
View File
@@ -0,0 +1,9 @@
#pragma once
#include <string>
namespace String
{
bool startsWith(const std::string& str, const std::string& prefix);
std::string reverse(const std::string& str);
}
+19
View File
@@ -0,0 +1,19 @@
#include <cctype>
#include <algorithm>
#include <Utility/String.hpp>
namespace String
{
bool startsWith(const std::string& str, const std::string& prefix)
{
return str.find(prefix) == 0;
}
std::string reverse(const std::string& str)
{
auto result = str;
std::reverse(result.begin(), result.end());
return result;
}
}
+25
View File
@@ -0,0 +1,25 @@
# To avoid repeating yourself in targets you can create your own target type (template). All properties of the template are inherited when used as a target type.
[project]
name = "templates"
description = "Target templates"
[template.app]
type = "executable"
sources = ["src/templates.cpp"]
compile-definitions = ["IS_APP"]
# Unlike interface targets you can also inherit properties
[template.app.properties]
CXX_STANDARD = "11"
CXX_STANDARD_REQUIRED = true
[target.app-a]
type = "app"
compile-definitions = ["APP_A"]
[target.app-b]
type = "app"
compile-definitions = ["APP_B"]
# **Note**: In most cases you probably want to use an [interface](/examples/interface) target instead.
+13
View File
@@ -0,0 +1,13 @@
#include <cstdio>
#if !defined(IS_APP)
#error Something went wrong with the template
#endif // IS_APP
int main() {
#if defined(APP_A)
puts("Hello from app A!");
#elif defined(APP_B)
puts("Hello from app B!");
#endif
}