mirror of
https://github.com/build-cpp/cmkr
synced 2026-06-08 13:22:55 +00:00
Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c76956450a | |||
| 1596a8143d | |||
| 9b0f18ee94 | |||
| 887086dc05 | |||
| edc8e1e02e | |||
| 1f6e31e0ef | |||
| 4de1500bb3 | |||
| 85370968f8 | |||
| 06a4c04df6 | |||
| fef2a0215b | |||
| fc74a5e802 | |||
| e48d402e21 | |||
| f6a245618e | |||
| c713606fd4 | |||
| fe9b6587a9 | |||
| a2f8d9e5c9 | |||
| c7925e7110 | |||
| c778a5fe2b | |||
| 16b2cbbb57 | |||
| 2f5c7823be | |||
| a29b6a8aaa | |||
| 5ac8b15c9a | |||
| e7d9faa6bb | |||
| 6f8b07a801 | |||
| e8c6ccb698 | |||
| 539ebc8f4b | |||
| 37ca385673 | |||
| 8ae029dab1 | |||
| fb16cc34b1 | |||
| 47703871e1 | |||
| a8d6b15dcb | |||
| 87b3c7ec6c | |||
| d905be1d13 | |||
| 8d13ccbeaa | |||
| bcbc9d2b20 | |||
| 05324f593b | |||
| 2b7ee72e86 | |||
| 61d5e64d87 | |||
| eed1e38407 | |||
| 61851d2bee | |||
| c0d665095e | |||
| 6c1e388391 | |||
| 04905371b8 | |||
| 5a6ac44fcf | |||
| 9db82dfd05 | |||
| 459ebb14b5 | |||
| 490869b9a2 | |||
| 8ae2754994 | |||
| 18419297f4 | |||
| bdaf5c94e6 | |||
| fa3b7a346c | |||
| df1dbf7953 | |||
| 5951f6930e | |||
| a6d5f1c99b | |||
| dc852b9a2f | |||
| 66621a9818 | |||
| 2cd8ad0f18 | |||
| 084cbd6159 | |||
| b4b0f533a9 | |||
| 0acc9b4e5a | |||
| 61dfb0eb60 | |||
| 3201fd052e | |||
| 83087ff06e | |||
| 88e9241e9f | |||
| eff967277d | |||
| ea01497246 |
@@ -0,0 +1 @@
|
||||
github: [mrexodia]
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
- name: Test
|
||||
run: |
|
||||
cd build/tests
|
||||
ctest -C ${{ env.BUILD_TYPE }}
|
||||
ctest -C ${{ env.BUILD_TYPE }} --verbose
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
string: ${{ runner.os }}
|
||||
|
||||
- name: Compress artifacts
|
||||
uses: papeloto/action-zip@v1
|
||||
uses: vimtor/action-zip@v1
|
||||
with:
|
||||
files: install/bin/
|
||||
dest: ${{ github.event.repository.name }}-${{ steps.osname.outputs.lowercase }}.zip
|
||||
|
||||
Generated
+1
-2
@@ -25,7 +25,7 @@ project(cmkr
|
||||
LANGUAGES
|
||||
CXX
|
||||
VERSION
|
||||
0.1.4
|
||||
0.2.12
|
||||
DESCRIPTION
|
||||
"CMakeLists generator from TOML"
|
||||
)
|
||||
@@ -86,7 +86,6 @@ list(APPEND cmkr_SOURCES
|
||||
"include/arguments.hpp"
|
||||
"include/build.hpp"
|
||||
"include/cmake_generator.hpp"
|
||||
"include/enum_helper.hpp"
|
||||
"include/error.hpp"
|
||||
"include/fs.hpp"
|
||||
"include/help.hpp"
|
||||
|
||||
@@ -7,29 +7,48 @@
|
||||
```toml
|
||||
[project]
|
||||
name = "cmkr_for_beginners"
|
||||
description = "A minimal cmkr project."
|
||||
|
||||
[target.hello_world]
|
||||
type = "executable"
|
||||
sources = ["src/main.cpp"]
|
||||
```
|
||||
|
||||
`cmkr` can bootstrap itself from CMake and consumers of your project do not need to install anything to work with it.
|
||||
`cmkr` can bootstrap itself from CMake and you only need CMake to use it.
|
||||
|
||||
## Getting started
|
||||
|
||||
The easiest way to get started is to use the [cmkr_for_beginners](https://github.com/build-cpp/cmkr_for_beginners) template repository. Either open it in [Gitpod](https://gitpod.io/#https://github.com/build-cpp/cmkr_for_beginners), or clone the repository and run:
|
||||
To get started run the following commands from your project directory:
|
||||
|
||||
```sh
|
||||
curl https://raw.githubusercontent.com/build-cpp/cmkr/main/cmake/cmkr.cmake -o cmkr.cmake
|
||||
cmake -P cmkr.cmake
|
||||
```
|
||||
|
||||
After the bootstrapping process finishes, modify [`cmake.toml`](https://build-cpp.github.io/cmkr/cmake-toml) and open the project in your favorite IDE or build with CMake:
|
||||
|
||||
```sh
|
||||
cmake -B build
|
||||
cmake --build build
|
||||
```
|
||||
|
||||
Alternatively you can check out the [cmkr topic](https://github.com/topics/cmkr), the [build-cpp organization](https://github.com/build-cpp) or the [tests](https://github.com/build-cpp/cmkr/tree/main/tests) for more examples and templates. You can also check out the [documentation](https://build-cpp.github.io/cmkr).
|
||||
Once bootstrapped, `cmkr` does not introduce extra steps to your workflow. After modifying `cmake.toml` you simply build/configure your CMake project and `cmkr` will automatically regenerate `CMakeLists.txt`.
|
||||
|
||||
In CI settings the `cmkr` bootstrapping process is skipped so there is no extra configure-time overhead in your pipelines.
|
||||
|
||||
## Template repositories
|
||||
|
||||
Another way to get started is to use the [cmkr_for_beginners](https://github.com/build-cpp/cmkr_for_beginners) template repository. Either open it in [Gitpod](https://gitpod.io/#https://github.com/build-cpp/cmkr_for_beginners), or clone the repository and run:
|
||||
|
||||
```sh
|
||||
cmake -B build
|
||||
cmake --build build
|
||||
```
|
||||
|
||||
Check out the [cmkr topic](https://github.com/topics/cmkr), the [build-cpp organization](https://github.com/build-cpp) or the [tests](https://github.com/build-cpp/cmkr/tree/main/tests) for more examples and templates.
|
||||
|
||||
## Command line
|
||||
|
||||
Optionally you can install `cmkr` in your `PATH` and use it as a utility from the command line:
|
||||
Optionally you can put a [`cmkr` release](https://github.com/build-cpp/cmkr/releases) in your `PATH` and use it as a utility from the command line:
|
||||
|
||||
```
|
||||
Usage: cmkr [arguments]
|
||||
@@ -50,3 +69,4 @@ arguments:
|
||||
- https://github.com/ToruNiina/toml11
|
||||
- https://github.com/mpark/variant
|
||||
- https://www.svgrepo.com/svg/192268/hammer
|
||||
- https://github.com/can1357 for buying `cmkr.build` ❤️
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ cmkr-include = false
|
||||
|
||||
[project]
|
||||
name = "cmkr"
|
||||
version = "0.1.4"
|
||||
version = "0.2.12"
|
||||
description = "CMakeLists generator from TOML"
|
||||
languages = ["CXX"]
|
||||
subdirs = ["third_party", "tests"]
|
||||
|
||||
@@ -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}")
|
||||
+52
-12
@@ -1,17 +1,22 @@
|
||||
include_guard()
|
||||
|
||||
if(CMAKE_SCRIPT_MODE_FILE)
|
||||
message(FATAL_ERROR "Running cmkr.cmake as a script is unsupported. To build your project try: cmake -B build")
|
||||
endif()
|
||||
|
||||
# Change these defaults to point to your infrastructure if desired
|
||||
set(CMKR_REPO "https://github.com/build-cpp/cmkr" CACHE STRING "cmkr git repository" FORCE)
|
||||
set(CMKR_TAG "archive_52671dcf" 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
|
||||
if(CMAKE_SCRIPT_MODE_FILE)
|
||||
set(CMAKE_BINARY_DIR "${CMAKE_BINARY_DIR}/build")
|
||||
set(CMAKE_CURRENT_BINARY_DIR "${CMAKE_BINARY_DIR}")
|
||||
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}")
|
||||
endif()
|
||||
|
||||
# Set these from the command line to customize for development/debugging purposes
|
||||
set(CMKR_EXECUTABLE "" CACHE FILEPATH "cmkr executable")
|
||||
set(CMKR_SKIP_GENERATION OFF CACHE BOOL "skip automatic cmkr generation")
|
||||
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
|
||||
if(DEFINED ENV{CI} OR CMKR_SKIP_GENERATION OR CMKR_BUILD_SKIP_GENERATION)
|
||||
@@ -23,7 +28,7 @@ if(DEFINED ENV{CI} OR CMKR_SKIP_GENERATION OR CMKR_BUILD_SKIP_GENERATION)
|
||||
endif()
|
||||
|
||||
# Disable cmkr if no cmake.toml file is found
|
||||
if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake.toml")
|
||||
if(NOT CMAKE_SCRIPT_MODE_FILE AND NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake.toml")
|
||||
message(AUTHOR_WARNING "[cmkr] Not found: ${CMAKE_CURRENT_SOURCE_DIR}/cmake.toml")
|
||||
macro(cmkr)
|
||||
endmacro()
|
||||
@@ -59,6 +64,8 @@ if(DEFINED ENV{CMKR_CACHE} AND EXISTS "$ENV{CMKR_CACHE}")
|
||||
if(NOT CMKR_DIRECTORY_PREFIX MATCHES "\\/$")
|
||||
set(CMKR_DIRECTORY_PREFIX "${CMKR_DIRECTORY_PREFIX}/")
|
||||
endif()
|
||||
# Build in release mode for the cache
|
||||
set(CMKR_BUILD_TYPE "Release")
|
||||
else()
|
||||
set(CMKR_DIRECTORY_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/_cmkr_")
|
||||
endif()
|
||||
@@ -66,7 +73,7 @@ set(CMKR_DIRECTORY "${CMKR_DIRECTORY_PREFIX}${CMKR_TAG}")
|
||||
set(CMKR_CACHED_EXECUTABLE "${CMKR_DIRECTORY}/bin/${CMKR_EXECUTABLE_NAME}")
|
||||
|
||||
# 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(DEFINED ENV{CMKR_CACHE} AND EXISTS "$ENV{CMKR_CACHE}")
|
||||
message(AUTHOR_WARNING "[cmkr] Switching to cached cmkr: '${CMKR_CACHED_EXECUTABLE}'")
|
||||
@@ -95,7 +102,7 @@ elseif(NOT CMKR_EXECUTABLE AND EXISTS "${CMKR_CACHED_EXECUTABLE}")
|
||||
else()
|
||||
set(CMKR_EXECUTABLE "${CMKR_CACHED_EXECUTABLE}" CACHE FILEPATH "Full path to cmkr executable" FORCE)
|
||||
message(VERBOSE "[cmkr] Bootstrapping '${CMKR_EXECUTABLE}'")
|
||||
|
||||
|
||||
message(STATUS "[cmkr] Fetching cmkr...")
|
||||
if(EXISTS "${CMKR_DIRECTORY}")
|
||||
cmkr_exec("${CMAKE_COMMAND}" -E rm -rf "${CMKR_DIRECTORY}")
|
||||
@@ -109,6 +116,16 @@ else()
|
||||
${CMKR_REPO}
|
||||
"${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)...")
|
||||
cmkr_exec("${CMAKE_COMMAND}"
|
||||
--no-warn-unused-cli
|
||||
@@ -143,6 +160,29 @@ if(NOT CMKR_EXEC_RESULT EQUAL 0)
|
||||
message(FATAL_ERROR "[cmkr] Failed to get version, try clearing the cache and rebuilding")
|
||||
endif()
|
||||
|
||||
# Use cmkr.cmake as a script
|
||||
if(CMAKE_SCRIPT_MODE_FILE)
|
||||
if(NOT EXISTS "${CMAKE_SOURCE_DIR}/cmake.toml")
|
||||
execute_process(COMMAND "${CMKR_EXECUTABLE}" init
|
||||
RESULT_VARIABLE CMKR_EXEC_RESULT
|
||||
)
|
||||
if(NOT CMKR_EXEC_RESULT EQUAL 0)
|
||||
message(FATAL_ERROR "[cmkr] Failed to bootstrap cmkr project. Please report an issue: https://github.com/build-cpp/cmkr/issues/new")
|
||||
else()
|
||||
message(STATUS "[cmkr] Modify cmake.toml and then configure using: cmake -B build")
|
||||
endif()
|
||||
else()
|
||||
execute_process(COMMAND "${CMKR_EXECUTABLE}" gen
|
||||
RESULT_VARIABLE CMKR_EXEC_RESULT
|
||||
)
|
||||
if(NOT CMKR_EXEC_RESULT EQUAL 0)
|
||||
message(FATAL_ERROR "[cmkr] Failed to generate project.")
|
||||
else()
|
||||
message(STATUS "[cmkr] Configure using: cmake -B build")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# This is the macro that contains black magic
|
||||
macro(cmkr)
|
||||
# When this macro is called from the generated file, fake some internal CMake variables
|
||||
@@ -176,18 +216,18 @@ macro(cmkr)
|
||||
# Copy the now-generated CMakeLists.txt to CMakerLists.txt
|
||||
# This is done because you cannot include() a file you are currently in
|
||||
configure_file(CMakeLists.txt "${CMKR_TEMP_FILE}" COPYONLY)
|
||||
|
||||
|
||||
# Add the macro required for the hack at the start of the cmkr macro
|
||||
set_source_files_properties("${CMKR_TEMP_FILE}" PROPERTIES
|
||||
CMKR_CURRENT_LIST_FILE "${CMAKE_CURRENT_LIST_FILE}"
|
||||
)
|
||||
|
||||
|
||||
# 'Execute' the newly-generated CMakeLists.txt
|
||||
include("${CMKR_TEMP_FILE}")
|
||||
|
||||
|
||||
# Delete the generated file
|
||||
file(REMOVE "${CMKR_TEMP_FILE}")
|
||||
|
||||
|
||||
# Do not execute the rest of the original CMakeLists.txt
|
||||
return()
|
||||
endif()
|
||||
|
||||
@@ -46,14 +46,14 @@ function(generate_documentation)
|
||||
string(REGEX MATCH "^(\n*(#[^\n]+\n)+\n*)" EXAMPLE_HEADER "${test_contents}")
|
||||
string(LENGTH "${EXAMPLE_HEADER}" header_length)
|
||||
string(STRIP "${EXAMPLE_HEADER}" EXAMPLE_HEADER)
|
||||
string(REGEX REPLACE "\n# ?" "\n" EXAMPLE_HEADER "\n${EXAMPLE_HEADER}")
|
||||
string(REGEX REPLACE "\n# ?" "\n\n" EXAMPLE_HEADER "\n${EXAMPLE_HEADER}")
|
||||
string(STRIP "${EXAMPLE_HEADER}" EXAMPLE_HEADER)
|
||||
|
||||
# Extract footer text
|
||||
string(REGEX MATCH "(((#[^\n]+)(\n+|$))+)$" EXAMPLE_FOOTER "${test_contents}")
|
||||
string(LENGTH "${EXAMPLE_FOOTER}" footer_length)
|
||||
string(STRIP "${EXAMPLE_FOOTER}" EXAMPLE_FOOTER)
|
||||
string(REGEX REPLACE "\n# ?" "\n" EXAMPLE_FOOTER "\n${EXAMPLE_FOOTER}")
|
||||
string(REGEX REPLACE "\n# ?" "\n\n" EXAMPLE_FOOTER "\n${EXAMPLE_FOOTER}")
|
||||
string(STRIP "${EXAMPLE_FOOTER}" EXAMPLE_FOOTER)
|
||||
|
||||
# Extract toml body
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
_site/
|
||||
.jekyll-metadata
|
||||
assets/
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
cmkr.build
|
||||
@@ -3,6 +3,7 @@ gem "minima", "~> 2.5"
|
||||
gem "github-pages", group: :jekyll_plugins
|
||||
|
||||
gem "jekyll-remote-theme"
|
||||
gem "jekyll-sitemap", "~> 1.4.0"
|
||||
|
||||
platforms :mingw, :x64_mingw, :mswin, :jruby do
|
||||
gem "tzinfo", "~> 1.2"
|
||||
|
||||
@@ -271,6 +271,7 @@ PLATFORMS
|
||||
DEPENDENCIES
|
||||
github-pages
|
||||
jekyll-remote-theme
|
||||
jekyll-sitemap (~> 1.4.0)
|
||||
just-the-docs
|
||||
minima (~> 2.5)
|
||||
tzinfo (~> 1.2)
|
||||
|
||||
+5
-2
@@ -2,6 +2,7 @@ title: cmkr
|
||||
|
||||
plugins:
|
||||
- jekyll-remote-theme
|
||||
- jekyll-sitemap
|
||||
|
||||
# Automatically deduce dark theme preference https://github.com/pmarsceill/just-the-docs/pull/464
|
||||
remote_theme: build-cpp/just-the-docs@light-switch
|
||||
@@ -9,7 +10,7 @@ search_enabled: true
|
||||
color_scheme: light-or-dark
|
||||
heading_anchors: true
|
||||
aux_links:
|
||||
"cmkr":
|
||||
"GitHub":
|
||||
- "https://github.com/build-cpp/cmkr"
|
||||
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_branch: "main"
|
||||
gh_edit_view_mode: "edit"
|
||||
gh_edit_source: docs
|
||||
gh_edit_source: docs
|
||||
|
||||
production_url : https://cmkr.build
|
||||
+30
-19
@@ -1,15 +1,15 @@
|
||||
---
|
||||
layout: page
|
||||
title: cmake.toml
|
||||
title: Reference
|
||||
permalink: /cmake-toml/
|
||||
nav_order: 3
|
||||
nav_order: 1
|
||||
---
|
||||
|
||||
# cmake.toml
|
||||
# Reference
|
||||
|
||||
This page is supposed to be a reference for the options in the `cmake.toml` file. If you think anything is missing or unclear, please [edit this page](https://github.com/build-cpp/cmkr/edit/main/docs/cmake-toml.md) or open an [issue](https://github.com/build-cpp/cmkr/issues).
|
||||
This page is a reference for the options in the `cmake.toml` file. If you think anything is missing or unclear, please [edit this page](https://github.com/build-cpp/cmkr/edit/main/docs/cmake-toml.md) or open an [issue](https://github.com/build-cpp/cmkr/issues).
|
||||
|
||||
See the [examples](/examples) section for more real-world examples.
|
||||
See the [examples](/examples) section for concrete examples.
|
||||
|
||||
## CMake configuration
|
||||
|
||||
@@ -47,6 +47,8 @@ arch64 = "CMAKE_SIZEOF_VOID_P EQUALS 8"
|
||||
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:
|
||||
|
||||
```toml
|
||||
@@ -56,7 +58,7 @@ sources = ["src/main.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
|
||||
[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\""
|
||||
clang = "CMAKE_CXX_COMPILER_ID MATCHES \"Clang\" OR CMAKE_C_COMPILER_ID MATCHES \"Clang\""
|
||||
msvc = "MSVC"
|
||||
root = "CMKR_ROOT_PROJECT"
|
||||
x64 = "CMAKE_SIZEOF_VOID_P EQUAL 8"
|
||||
x32 = "CMAKE_SIZEOF_VOID_P EQUAL 4"
|
||||
```
|
||||
|
||||
## Subdirectories
|
||||
|
||||
```toml
|
||||
[subdir.mysubdir]
|
||||
condition = "linux"
|
||||
condition = "mycondition"
|
||||
cmake-before = """
|
||||
message(STATUS "CMake injected before the add_subdirectory() call"
|
||||
"""
|
||||
@@ -96,14 +101,13 @@ packages = ["fmt", "zlib"]
|
||||
|
||||
The vcpkg `version` will automatically generate the `url` from the [official repository](https://github.com/microsoft/vcpkg/releases). For a custom registry you can specify your own `url` (and omit the `version`). You can browse available packages on [vcpkg.io](https://vcpkg.io/en/packages.html).
|
||||
|
||||
To specify package features you can use the following syntax: `imgui[docking-experimental,freetype,sdl2-binding,opengl3-binding]`.
|
||||
|
||||
## Packages
|
||||
|
||||
```toml
|
||||
[find-package]
|
||||
mypackage = { version = "1.0", required = true, config = true, components = ["mycomponent"] }
|
||||
|
||||
# Alternative syntax
|
||||
[find-package.mypackage]
|
||||
condition = "mycondition"
|
||||
version = "1.0"
|
||||
required = true
|
||||
config = true
|
||||
@@ -115,24 +119,29 @@ components = ["mycomponent"]
|
||||
**Note**: The `[fetch-content]` feature is unpolished and will likely change in a future release.
|
||||
|
||||
```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]
|
||||
condition = "mycondition"
|
||||
git = "https://github.com/myuser/gitcontent"
|
||||
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
|
||||
|
||||
```toml
|
||||
[target.mytarget]
|
||||
condition = "linux"
|
||||
condition = "mycondition"
|
||||
alias = "mytarget::mytarget"
|
||||
type = "static" # executable, library, shared, static, interface, custom
|
||||
type = "static" # executable, shared (DLL), static, interface, object, library, custom
|
||||
headers = ["src/mytarget.h"]
|
||||
sources = ["src/mytarget.cpp"]
|
||||
|
||||
@@ -178,6 +187,7 @@ FOLDER = "MyFolder"
|
||||
```toml
|
||||
# You can declare as many as you want like this, but the name has to be unique
|
||||
[[test]]
|
||||
condition = "mycondition"
|
||||
name = "mytest"
|
||||
command = "$<TARGET_FILE:mytest>"
|
||||
arguments = ["arg1", "arg2"]
|
||||
@@ -187,6 +197,7 @@ working-directory = "mytest-dir"
|
||||
|
||||
```toml
|
||||
[[install]]
|
||||
condition = "mycondition"
|
||||
targets = ["mytarget", "mytest"]
|
||||
destination = ["bin"]
|
||||
files = ["content/my.png"]
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
---
|
||||
layout: page
|
||||
title: Command line
|
||||
permalink: /command-line/
|
||||
nav_order: 2
|
||||
---
|
||||
|
||||
# Command line
|
||||
|
||||
Optionally you can install `cmkr` in your `PATH` and use it as a utility from the command line:
|
||||
|
||||
```
|
||||
Usage: cmkr [arguments]
|
||||
arguments:
|
||||
init [executable|library|shared|static|interface] Starts a new project in the same directory.
|
||||
gen Generates CMakeLists.txt file.
|
||||
build <extra cmake args> Run cmake and build.
|
||||
install Run cmake --install. Needs admin privileges.
|
||||
clean Clean the build directory.
|
||||
help Show help.
|
||||
version Current cmkr version.
|
||||
```
|
||||
@@ -19,6 +19,7 @@ description = "Header-only library"
|
||||
[target.mylib]
|
||||
type = "interface"
|
||||
include-directories = ["include"]
|
||||
compile-features = ["cxx_std_11"]
|
||||
|
||||
[target.example]
|
||||
type = "executable"
|
||||
|
||||
@@ -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>
|
||||
@@ -33,4 +33,6 @@ link-libraries = ["fmt::fmt"]
|
||||
|
||||
The bootstrapping of vcpkg is fully automated and no user interaction is necessary. You can disable vcpkg by setting `CMKR_DISABLE_VCPKG=ON`.
|
||||
|
||||
To specify package features you can use the following syntax: `imgui[docking-experimental,freetype,sdl2-binding,opengl3-binding]`.
|
||||
|
||||
<sup><sub>This page was automatically generated from [tests/vcpkg/cmake.toml](https://github.com/build-cpp/cmkr/tree/main/tests/vcpkg/cmake.toml).</sub></sup>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
layout: null
|
||||
permalink: /getting-started/
|
||||
---
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="refresh" content="0;url=https://cmkr.build">
|
||||
</head>
|
||||
</html>
|
||||
@@ -1,17 +0,0 @@
|
||||
---
|
||||
layout: page
|
||||
title: Getting started
|
||||
permalink: /getting-started/
|
||||
nav_order: 1
|
||||
---
|
||||
|
||||
# Getting started
|
||||
|
||||
The easiest way to get started is to use the [cmkr_for_beginners](https://github.com/build-cpp/cmkr_for_beginners) template repository. Either open it in [Gitpod](https://gitpod.io/#https://github.com/build-cpp/cmkr_for_beginners), or clone the repository and run:
|
||||
|
||||
```sh
|
||||
cmake -B build
|
||||
cmake --build build
|
||||
```
|
||||
|
||||
Alternatively you can check out the [cmkr topic](https://github.com/topics/cmkr) or the [build-cpp organization](https://github.com/build-cpp) for more examples and templates.
|
||||
+57
-3
@@ -6,18 +6,72 @@ nav_order: 0
|
||||
|
||||
# Index
|
||||
|
||||
`cmkr`, pronounced "cmaker", is a modern build system based on [CMake](https://cmake.org/) and [TOML](https://toml.io). It was originally created by [Mohammed Alyousef](https://github.com/MoAlyousef).
|
||||
`cmkr`, pronounced "cmaker", is a modern build system based on [CMake](https://cmake.org/) and [TOML](https://toml.io).
|
||||
|
||||
`cmkr` parses `cmake.toml` files and generates a modern, idiomatic `CMakeLists.txt` for you. A minimal example:
|
||||
|
||||
```toml
|
||||
[project]
|
||||
name = "cmkr_for_beginners"
|
||||
description = "A minimal cmkr project."
|
||||
|
||||
[target.hello_world]
|
||||
type = "executable"
|
||||
sources = ["src/main.cpp"]
|
||||
```
|
||||
|
||||
`cmkr` can bootstrap itself from CMake and consumers of your project do not need to install anything to work with it.
|
||||
`cmkr` can bootstrap itself from CMake and you only need CMake to use it.
|
||||
|
||||
## Getting started
|
||||
|
||||
To get started run the following commands from your project directory:
|
||||
|
||||
```sh
|
||||
curl https://raw.githubusercontent.com/build-cpp/cmkr/main/cmake/cmkr.cmake -o cmkr.cmake
|
||||
cmake -P cmkr.cmake
|
||||
```
|
||||
|
||||
After the bootstrapping process finishes, modify [`cmake.toml`](https://build-cpp.github.io/cmkr/cmake-toml) and open the project in your favorite IDE or build with CMake:
|
||||
|
||||
```sh
|
||||
cmake -B build
|
||||
cmake --build build
|
||||
```
|
||||
|
||||
Once bootstrapped, `cmkr` does not introduce extra steps to your workflow. After modifying `cmake.toml` you simply build/configure your CMake project and `cmkr` will automatically regenerate `CMakeLists.txt`.
|
||||
|
||||
In CI settings the `cmkr` bootstrapping process is skipped so there is no extra configure-time overhead in your pipelines.
|
||||
|
||||
## Template repositories
|
||||
|
||||
Another way to get started is to use the [cmkr_for_beginners](https://github.com/build-cpp/cmkr_for_beginners) template repository. Either open it in [Gitpod](https://gitpod.io/#https://github.com/build-cpp/cmkr_for_beginners), or clone the repository and run:
|
||||
|
||||
```sh
|
||||
cmake -B build
|
||||
cmake --build build
|
||||
```
|
||||
|
||||
Check out the [cmkr topic](https://github.com/topics/cmkr), the [build-cpp organization](https://github.com/build-cpp) or the [tests](https://github.com/build-cpp/cmkr/tree/main/tests) for more examples and templates.
|
||||
|
||||
## Command line
|
||||
|
||||
Optionally you can put a [`cmkr` release](https://github.com/build-cpp/cmkr/releases) in your `PATH` and use it as a utility from the command line:
|
||||
|
||||
```
|
||||
Usage: cmkr [arguments]
|
||||
arguments:
|
||||
init [executable|library|shared|static|interface] Starts a new project in the same directory.
|
||||
gen Generates CMakeLists.txt file.
|
||||
build <extra cmake args> Run cmake and build.
|
||||
install Run cmake --install. Needs admin privileges.
|
||||
clean Clean the build directory.
|
||||
help Show help.
|
||||
version Current cmkr version.
|
||||
```
|
||||
|
||||
## Credits
|
||||
|
||||
- https://github.com/gulrak/filesystem
|
||||
- https://github.com/Tessil/ordered-map
|
||||
- https://github.com/ToruNiina/toml11
|
||||
- https://github.com/mpark/variant
|
||||
- https://www.svgrepo.com/svg/192268/hammer
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -91,10 +91,10 @@ static const char *toml_migration = &R"lit(
|
||||
name = "@name"
|
||||
|
||||
# TODO: define a target for each of your executables/libraries like this:
|
||||
#[target.@name]
|
||||
#type = "executable"
|
||||
#[target.myexecutable]
|
||||
#type = "executable" # static, shared
|
||||
#sources = ["src/@name/*.cpp", "include/@name/*.hpp"]
|
||||
#include-directories = ["include"]
|
||||
#compile-features = ["cxx_std_11"]
|
||||
#link-libraries = ["other-targets"]
|
||||
#link-libraries = ["target-or-library"]
|
||||
)lit"[1]; // skip initial newline
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
namespace cmkr {
|
||||
namespace parser {
|
||||
|
||||
template <typename T>
|
||||
using Condition = tsl::ordered_map<std::string, T>;
|
||||
|
||||
using ConditionVector = Condition<std::vector<std::string>>;
|
||||
|
||||
struct Setting {
|
||||
std::string name;
|
||||
std::string comment;
|
||||
@@ -22,8 +27,9 @@ struct Option {
|
||||
bool val = false;
|
||||
};
|
||||
|
||||
struct Package {
|
||||
struct FindPackage {
|
||||
std::string name;
|
||||
std::string condition;
|
||||
std::string version;
|
||||
bool required = true;
|
||||
bool config = false;
|
||||
@@ -33,7 +39,13 @@ struct Package {
|
||||
struct Vcpkg {
|
||||
std::string version;
|
||||
std::string url;
|
||||
std::vector<std::string> packages;
|
||||
|
||||
struct Package {
|
||||
std::string name;
|
||||
std::vector<std::string> features;
|
||||
};
|
||||
|
||||
std::vector<Package> packages;
|
||||
};
|
||||
|
||||
enum TargetType {
|
||||
@@ -44,16 +56,16 @@ enum TargetType {
|
||||
target_interface,
|
||||
target_custom,
|
||||
target_object,
|
||||
target_template,
|
||||
target_last,
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using Condition = tsl::ordered_map<std::string, T>;
|
||||
|
||||
using ConditionVector = Condition<std::vector<std::string>>;
|
||||
extern const char *targetTypeNames[target_last];
|
||||
|
||||
struct Target {
|
||||
std::string name;
|
||||
TargetType type = {};
|
||||
TargetType type = target_last;
|
||||
std::string type_name;
|
||||
|
||||
ConditionVector headers;
|
||||
ConditionVector sources;
|
||||
@@ -93,8 +105,15 @@ struct Target {
|
||||
ConditionVector include_after;
|
||||
};
|
||||
|
||||
struct Template {
|
||||
Target outline;
|
||||
std::string add_function;
|
||||
bool pass_sources_to_add_function = false;
|
||||
};
|
||||
|
||||
struct Test {
|
||||
std::string name;
|
||||
std::string condition;
|
||||
std::vector<std::string> configurations;
|
||||
std::string working_directory;
|
||||
std::string command;
|
||||
@@ -102,16 +121,19 @@ struct Test {
|
||||
};
|
||||
|
||||
struct Install {
|
||||
std::string condition;
|
||||
std::vector<std::string> targets;
|
||||
std::vector<std::string> files;
|
||||
std::vector<std::string> dirs;
|
||||
std::vector<std::string> configs;
|
||||
std::string destination;
|
||||
std::string component;
|
||||
};
|
||||
|
||||
struct Subdir {
|
||||
std::string name;
|
||||
std::string condition;
|
||||
|
||||
Condition<std::string> cmake_before;
|
||||
Condition<std::string> cmake_after;
|
||||
ConditionVector include_before;
|
||||
@@ -120,7 +142,21 @@ struct Subdir {
|
||||
|
||||
struct Content {
|
||||
std::string name;
|
||||
std::string condition;
|
||||
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 {
|
||||
@@ -147,9 +183,11 @@ struct Project {
|
||||
ConditionVector include_after;
|
||||
std::vector<Setting> settings;
|
||||
std::vector<Option> options;
|
||||
std::vector<Package> packages;
|
||||
std::vector<FindPackage> packages;
|
||||
Vcpkg vcpkg;
|
||||
Package package;
|
||||
std::vector<Content> contents;
|
||||
std::vector<Template> templates;
|
||||
std::vector<Target> targets;
|
||||
std::vector<Test> tests;
|
||||
std::vector<Install> installs;
|
||||
|
||||
+445
-217
@@ -6,21 +6,13 @@
|
||||
#include "fs.hpp"
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace cmkr {
|
||||
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) {
|
||||
std::string s = format;
|
||||
for (const auto &itr : variables) {
|
||||
@@ -33,19 +25,23 @@ static std::string format(const char *format, tsl::ordered_map<std::string, std:
|
||||
return s;
|
||||
}
|
||||
|
||||
static std::vector<std::string> expand_cmake_path(const fs::path &name, const fs::path &toml_dir) {
|
||||
std::vector<std::string> temp;
|
||||
static std::string extract_suffix(const fs::path &base, const fs::path &full) {
|
||||
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) {
|
||||
auto fullpath = full.string();
|
||||
auto base_len = base.string().length();
|
||||
auto delet = fullpath.substr(base_len + 1, fullpath.length() - base_len);
|
||||
return delet;
|
||||
};
|
||||
static std::vector<std::string> expand_cmake_path(const fs::path &name, const fs::path &toml_dir, bool root_project) {
|
||||
std::vector<std::string> temp;
|
||||
|
||||
auto stem = name.filename().stem().string();
|
||||
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 == "*") {
|
||||
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) {
|
||||
@@ -70,11 +66,11 @@ static std::vector<std::string> expand_cmake_path(const fs::path &name, const fs
|
||||
return temp;
|
||||
}
|
||||
|
||||
static std::vector<std::string> expand_cmake_paths(const std::vector<std::string> &sources, const fs::path &toml_dir) {
|
||||
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
|
||||
std::vector<std::string> result;
|
||||
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) {
|
||||
result.push_back(f);
|
||||
}
|
||||
@@ -105,8 +101,11 @@ void generate_project(const std::string &type) {
|
||||
};
|
||||
|
||||
if (!fs::is_empty(fs::current_path())) {
|
||||
// Make a backup of an existing CMakeLists.txt if it exists
|
||||
std::error_code ec;
|
||||
fs::rename("CMakeLists.txt", "CMakeLists.txt.bak", ec);
|
||||
// Create an empty cmake.toml for migration purporses
|
||||
create_file("cmake.toml", format(toml_migration, variables));
|
||||
puts("Generated migration cmake.toml in existing project directory...");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -158,6 +157,10 @@ struct Command {
|
||||
}
|
||||
|
||||
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
|
||||
if (str.find(' ') == std::string::npos && str.find('\"') == std::string::npos && str.find('/') == std::string::npos &&
|
||||
str.find(';') == std::string::npos) {
|
||||
@@ -231,9 +234,16 @@ struct Command {
|
||||
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>
|
||||
bool print_arg(const std::pair<K, V> &kv) {
|
||||
if (kv.second.empty()) {
|
||||
if (is_empty(kv.second)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -247,7 +257,7 @@ struct Command {
|
||||
}
|
||||
|
||||
bool print_arg(const RawArg &arg) {
|
||||
if (arg.arg.empty()) {
|
||||
if (is_empty(arg.arg)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -313,10 +323,10 @@ static std::string tolf(const std::string &str) {
|
||||
};
|
||||
|
||||
struct Generator {
|
||||
Generator(parser::Project &project) : project(project) {}
|
||||
Generator(const parser::Project &project) : project(project) {}
|
||||
Generator(const Generator &) = delete;
|
||||
|
||||
parser::Project &project;
|
||||
const parser::Project &project;
|
||||
std::stringstream ss;
|
||||
int indent = 0;
|
||||
|
||||
@@ -380,11 +390,7 @@ struct Generator {
|
||||
for (const auto &itr : value) {
|
||||
const auto &condition = itr.first;
|
||||
if (!condition.empty()) {
|
||||
if (project.conditions.count(condition) == 0) {
|
||||
// TODO: somehow print line number information here?
|
||||
throw std::runtime_error("Unknown condition '" + condition + "'");
|
||||
}
|
||||
cmd("if", condition)(RawArg(project.conditions[condition]));
|
||||
cmd("if", condition)(RawArg(project.conditions.at(condition)));
|
||||
}
|
||||
|
||||
if (!itr.second.empty()) {
|
||||
@@ -398,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) {
|
||||
@@ -451,6 +486,9 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
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);
|
||||
Generator gen(project);
|
||||
|
||||
@@ -459,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 comment = [&gen](const std::string &comment) { return gen.comment(comment); };
|
||||
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";
|
||||
comment("This file is automatically generated from cmake.toml - DO NOT EDIT");
|
||||
comment("See " + cmkr_url + " for more information");
|
||||
endl();
|
||||
|
||||
// Root project doesn't have a parent
|
||||
if (parent_project == nullptr) {
|
||||
if (root_project) {
|
||||
cmd("cmake_minimum_required")("VERSION", project.cmake_version).endl();
|
||||
|
||||
if (!project.allow_in_tree) {
|
||||
@@ -538,121 +573,10 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
ss << "\")\n\n";
|
||||
}
|
||||
|
||||
gen.handle_condition(project.include_before, [&](const std::string &, const std::vector<std::string> &includes) { inject_includes(includes); });
|
||||
gen.handle_condition(project.cmake_before, [&](const std::string &, const std::string &cmake) { inject_cmake(cmake); });
|
||||
|
||||
if (!project.project_name.empty()) {
|
||||
auto languages = std::make_pair("LANGUAGES", project.project_languages);
|
||||
auto version = std::make_pair("VERSION", project.project_version);
|
||||
auto description = std::make_pair("DESCRIPTION", project.project_description);
|
||||
cmd("project")(project.project_name, languages, version, description).endl();
|
||||
}
|
||||
|
||||
gen.handle_condition(project.include_after, [&](const std::string &, const std::vector<std::string> &includes) { inject_includes(includes); });
|
||||
gen.handle_condition(project.cmake_after, [&](const std::string &, const std::string &cmake) { inject_cmake(cmake); });
|
||||
|
||||
if (!project.contents.empty()) {
|
||||
cmd("include")("FetchContent").endl();
|
||||
for (const auto &content : project.contents) {
|
||||
cmd("message")("STATUS", "Fetching " + content.name + "...");
|
||||
ss << "FetchContent_Declare(\n\t" << content.name << "\n";
|
||||
for (const auto &arg : content.arguments) {
|
||||
ss << "\t" << arg.first << "\n\t\t" << arg.second << "\n";
|
||||
}
|
||||
ss << ")\n";
|
||||
cmd("FetchContent_MakeAvailable")(content.name).endl();
|
||||
}
|
||||
}
|
||||
|
||||
if (!project.vcpkg.packages.empty()) {
|
||||
// Allow the user to specify a url or derive it from the version
|
||||
auto url = project.vcpkg.url;
|
||||
if (url.empty()) {
|
||||
if (project.vcpkg.version.empty()) {
|
||||
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";
|
||||
}
|
||||
|
||||
// Show a nicer error than vcpkg when specifying an invalid package name
|
||||
for (const auto &package : project.vcpkg.packages) {
|
||||
if (!vcpkg_valid_identifier(package)) {
|
||||
throw std::runtime_error("Invalid [vcpkg].packages name '" + package + "' (needs to be lowercase alphanumeric)");
|
||||
}
|
||||
}
|
||||
|
||||
// CMake to bootstrap vcpkg and download the packages
|
||||
// clang-format off
|
||||
cmd("if")("CMKR_ROOT_PROJECT", "AND", "NOT", "CMKR_DISABLE_VCPKG");
|
||||
cmd("include")("FetchContent");
|
||||
cmd("message")("STATUS", "Fetching vcpkg...");
|
||||
cmd("FetchContent_Declare")("vcpkg", "URL", url);
|
||||
cmd("FetchContent_MakeAvailable")("vcpkg");
|
||||
cmd("include")("${vcpkg_SOURCE_DIR}/scripts/buildsystems/vcpkg.cmake");
|
||||
cmd("endif")();
|
||||
endl();
|
||||
// clang-format on
|
||||
|
||||
// Generate vcpkg.json (sorry for the ugly string handling, nlohmann compiles very slowly)
|
||||
std::ofstream ofs("vcpkg.json", std::ios::binary);
|
||||
if (!ofs) {
|
||||
throw std::runtime_error("Failed to create a vcpkg.json manifest file!");
|
||||
}
|
||||
ofs << R"({
|
||||
"$cmkr": "This file is automatically generated from cmake.toml - DO NOT EDIT",
|
||||
"$cmkr-url": "https://github.com/build-cpp/cmkr",
|
||||
"$schema": "https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json",
|
||||
"dependencies": [
|
||||
)";
|
||||
|
||||
const auto &packages = project.vcpkg.packages;
|
||||
for (size_t i = 0; i < packages.size(); i++) {
|
||||
const auto &package = packages[i];
|
||||
if (!vcpkg_valid_identifier(package)) {
|
||||
throw std::runtime_error("Invalid vcpkg package name '" + package + "'");
|
||||
}
|
||||
ofs << " \"" << package << '\"';
|
||||
if (i + 1 < packages.size()) {
|
||||
ofs << ',';
|
||||
}
|
||||
ofs << '\n';
|
||||
}
|
||||
|
||||
auto escape = [](const std::string &str) {
|
||||
std::string result;
|
||||
for (auto ch : str) {
|
||||
if (ch == '\\' || ch == '\"') {
|
||||
result += '\\';
|
||||
}
|
||||
result += ch;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
ofs << " ],\n";
|
||||
ofs << " \"description\": \"" << escape(project.project_description) << "\",\n";
|
||||
ofs << " \"name\": \"" << escape(project.project_name) << "\",\n";
|
||||
ofs << R"( "version-string": "")" << '\n';
|
||||
ofs << "}\n";
|
||||
}
|
||||
|
||||
if (!project.packages.empty()) {
|
||||
comment("Packages");
|
||||
for (const auto &dep : project.packages) {
|
||||
auto version = dep.version;
|
||||
if (version == "*")
|
||||
version.clear();
|
||||
auto required = dep.required ? "REQUIRED" : "";
|
||||
auto config = dep.config ? "CONFIG" : "";
|
||||
auto components = std::make_pair("COMPONENTS", dep.components);
|
||||
cmd("find_package")(dep.name, version, required, config, components).endl();
|
||||
}
|
||||
}
|
||||
|
||||
if (!project.options.empty()) {
|
||||
comment("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();
|
||||
}
|
||||
@@ -678,6 +602,154 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
endl();
|
||||
}
|
||||
|
||||
gen.conditional_includes(project.include_before);
|
||||
gen.conditional_cmake(project.cmake_before);
|
||||
|
||||
if (!project.project_name.empty()) {
|
||||
auto languages = std::make_pair("LANGUAGES", project.project_languages);
|
||||
auto version = std::make_pair("VERSION", project.project_version);
|
||||
auto description = std::make_pair("DESCRIPTION", project.project_description);
|
||||
cmd("project")(project.project_name, languages, version, description).endl();
|
||||
}
|
||||
|
||||
gen.conditional_includes(project.include_after);
|
||||
gen.conditional_cmake(project.cmake_after);
|
||||
|
||||
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
|
||||
auto url = project.vcpkg.url;
|
||||
auto version_name = url;
|
||||
if (url.empty()) {
|
||||
if (project.vcpkg.version.empty()) {
|
||||
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";
|
||||
version_name = project.vcpkg.version;
|
||||
}
|
||||
|
||||
// Show a nicer error than vcpkg when specifying an invalid package name
|
||||
for (const auto &package : project.vcpkg.packages) {
|
||||
if (!vcpkg_valid_identifier(package.name)) {
|
||||
throw std::runtime_error("Invalid [vcpkg].packages name '" + package.name + "' (needs to be lowercase alphanumeric)");
|
||||
}
|
||||
}
|
||||
|
||||
// CMake to bootstrap vcpkg and download the packages
|
||||
// clang-format off
|
||||
cmd("if")("CMKR_ROOT_PROJECT", "AND", "NOT", "CMKR_DISABLE_VCPKG");
|
||||
cmd("include")("FetchContent");
|
||||
cmd("message")("STATUS", "Fetching vcpkg (" + version_name + ")...");
|
||||
cmd("FetchContent_Declare")("vcpkg", "URL", url);
|
||||
cmd("FetchContent_MakeAvailable")("vcpkg");
|
||||
cmd("include")("${vcpkg_SOURCE_DIR}/scripts/buildsystems/vcpkg.cmake");
|
||||
cmd("endif")();
|
||||
endl();
|
||||
// clang-format on
|
||||
|
||||
// Generate vcpkg.json (sorry for the ugly string handling, nlohmann compiles very slowly)
|
||||
std::ofstream ofs("vcpkg.json", std::ios::binary);
|
||||
if (!ofs) {
|
||||
throw std::runtime_error("Failed to create a vcpkg.json manifest file!");
|
||||
}
|
||||
ofs << R"({
|
||||
"$cmkr": "This file is automatically generated from cmake.toml - DO NOT EDIT",
|
||||
"$cmkr-url": "https://github.com/build-cpp/cmkr",
|
||||
"$schema": "https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json",
|
||||
"dependencies": [
|
||||
)";
|
||||
|
||||
const auto &packages = project.vcpkg.packages;
|
||||
for (size_t i = 0; i < packages.size(); i++) {
|
||||
const auto &package = packages[i];
|
||||
const auto &features = package.features;
|
||||
if (!vcpkg_valid_identifier(package.name)) {
|
||||
throw std::runtime_error("Invalid vcpkg package name '" + package.name + "'");
|
||||
}
|
||||
for (const auto &feature : features) {
|
||||
if (!vcpkg_valid_identifier(feature)) {
|
||||
throw std::runtime_error("Invalid vcpkg package feature '" + feature + "'");
|
||||
}
|
||||
}
|
||||
if (features.empty()) {
|
||||
ofs << " \"" << package.name << '\"';
|
||||
} else {
|
||||
ofs << " {\n";
|
||||
ofs << " \"name\": \"" << package.name << "\",\n";
|
||||
ofs << " \"features\": [";
|
||||
for (size_t j = 0; j < features.size(); j++) {
|
||||
const auto &feature = features[j];
|
||||
ofs << '\"' << feature << '\"';
|
||||
if (j + 1 < features.size()) {
|
||||
ofs << ',';
|
||||
}
|
||||
}
|
||||
ofs << "]\n";
|
||||
ofs << " }";
|
||||
}
|
||||
if (i + 1 < packages.size()) {
|
||||
ofs << ',';
|
||||
}
|
||||
ofs << '\n';
|
||||
}
|
||||
|
||||
auto escape = [](const std::string &str) {
|
||||
std::string result;
|
||||
for (auto ch : str) {
|
||||
if (ch == '\\' || ch == '\"') {
|
||||
result += '\\';
|
||||
}
|
||||
result += ch;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
ofs << " ],\n";
|
||||
ofs << " \"description\": \"" << escape(project.project_description) << "\",\n";
|
||||
ofs << " \"name\": \"" << escape(vcpkg_escape_identifier(project.project_name)) << "\",\n";
|
||||
ofs << R"( "version-string": "")" << '\n';
|
||||
ofs << "}\n";
|
||||
}
|
||||
|
||||
if (!project.contents.empty()) {
|
||||
cmd("include")("FetchContent").endl();
|
||||
for (const auto &content : project.contents) {
|
||||
ConditionScope cs(gen, content.condition);
|
||||
|
||||
gen.conditional_includes(content.include_before);
|
||||
gen.conditional_cmake(content.cmake_before);
|
||||
|
||||
std::string version_info = "";
|
||||
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") + ")";
|
||||
}
|
||||
cmd("message")("STATUS", "Fetching " + content.name + version_info + "...");
|
||||
cmd("FetchContent_Declare")(content.name, content.arguments);
|
||||
cmd("FetchContent_MakeAvailable")(content.name).endl();
|
||||
|
||||
gen.conditional_includes(content.include_after);
|
||||
gen.conditional_cmake(content.cmake_after);
|
||||
}
|
||||
}
|
||||
|
||||
if (!project.packages.empty()) {
|
||||
comment("Packages");
|
||||
for (const auto &dep : project.packages) {
|
||||
auto version = dep.version;
|
||||
if (version == "*")
|
||||
version.clear();
|
||||
auto required = dep.required ? "REQUIRED" : "";
|
||||
auto config = dep.config ? "CONFIG" : "";
|
||||
auto components = std::make_pair("COMPONENTS", dep.components);
|
||||
ConditionScope cs(gen, dep.condition);
|
||||
cmd("find_package")(dep.name, version, required, config, components).endl();
|
||||
}
|
||||
}
|
||||
|
||||
auto add_subdir = [&](const std::string &dir) {
|
||||
// clang-format off
|
||||
comment(dir);
|
||||
@@ -703,52 +775,69 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
endl();
|
||||
}
|
||||
for (const auto &subdir : project.subdirs) {
|
||||
if (!subdir.condition.empty()) {
|
||||
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]));
|
||||
}
|
||||
ConditionScope cs(gen, subdir.condition);
|
||||
|
||||
gen.handle_condition(subdir.include_before,
|
||||
[&](const std::string &, const std::vector<std::string> &includes) { inject_includes(includes); });
|
||||
gen.handle_condition(subdir.cmake_before, [&](const std::string &, const std::string &cmake) { inject_cmake(cmake); });
|
||||
gen.conditional_includes(subdir.include_before);
|
||||
gen.conditional_cmake(subdir.cmake_before);
|
||||
|
||||
add_subdir(subdir.name);
|
||||
|
||||
gen.handle_condition(subdir.include_after, [&](const std::string &, const std::vector<std::string> &includes) { inject_includes(includes); });
|
||||
gen.handle_condition(subdir.cmake_after, [&](const std::string &, const std::string &cmake) { inject_cmake(cmake); });
|
||||
|
||||
if (!subdir.condition.empty()) {
|
||||
cmd("endif")();
|
||||
}
|
||||
gen.conditional_includes(subdir.include_after);
|
||||
gen.conditional_cmake(subdir.cmake_after);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (!target.condition.empty()) {
|
||||
const auto &condition = target.condition;
|
||||
if (project.conditions.count(condition) == 0) {
|
||||
throw std::runtime_error("Unknown condition '" + condition + "' for [target." + target.name + "]");
|
||||
// Check if this target is using a template.
|
||||
if (target.type == parser::target_template) {
|
||||
for (const auto &t : project.templates) {
|
||||
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);
|
||||
|
||||
gen.handle_condition(target.include_before,
|
||||
[&](const std::string &, const std::vector<std::string> &includes) { inject_includes(includes); });
|
||||
gen.handle_condition(target.cmake_before, [&](const std::string &, const std::string &cmake) { inject_cmake(cmake); });
|
||||
if (tmplate != nullptr) {
|
||||
gen.conditional_includes(tmplate->outline.include_before);
|
||||
gen.conditional_cmake(tmplate->outline.cmake_before);
|
||||
}
|
||||
|
||||
gen.conditional_includes(target.include_before);
|
||||
gen.conditional_cmake(target.cmake_before);
|
||||
|
||||
auto sources_var = target.name + "_SOURCES";
|
||||
|
||||
bool added_toml = false;
|
||||
cmd("set")(sources_var, RawArg("\"\"")).endl();
|
||||
|
||||
if (tmplate != nullptr) {
|
||||
gen.handle_condition(tmplate->outline.sources, [&](const std::string &condition, const std::vector<std::string> &condition_sources) {
|
||||
auto sources = expand_cmake_paths(condition_sources, path, root_project);
|
||||
if (sources.empty()) {
|
||||
auto source_key = condition.empty() ? "sources" : (condition + ".sources");
|
||||
throw std::runtime_error(target.name + " " + source_key + " wildcard found 0 files");
|
||||
}
|
||||
cmd("list")("APPEND", sources_var, 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()) {
|
||||
auto source_key = condition.empty() ? "sources" : (condition + ".sources");
|
||||
throw std::runtime_error(target.name + " " + source_key + " wildcard found 0 files");
|
||||
@@ -760,66 +849,91 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
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("set")("CMKR_SOURCES", "${" + sources_var + "}");
|
||||
|
||||
std::string add_command;
|
||||
std::string target_type;
|
||||
std::string target_type_string;
|
||||
std::string target_scope;
|
||||
switch (target.type) {
|
||||
|
||||
switch (target_type) {
|
||||
case parser::target_executable:
|
||||
add_command = "add_executable";
|
||||
target_type = "";
|
||||
target_type_string = "";
|
||||
target_scope = "PRIVATE";
|
||||
break;
|
||||
case parser::target_library:
|
||||
add_command = "add_library";
|
||||
target_type = "";
|
||||
target_type_string = "";
|
||||
target_scope = "PUBLIC";
|
||||
break;
|
||||
case parser::target_shared:
|
||||
add_command = "add_library";
|
||||
target_type = "SHARED";
|
||||
target_type_string = "SHARED";
|
||||
target_scope = "PUBLIC";
|
||||
break;
|
||||
case parser::target_static:
|
||||
add_command = "add_library";
|
||||
target_type = "STATIC";
|
||||
target_type_string = "STATIC";
|
||||
target_scope = "PUBLIC";
|
||||
break;
|
||||
case parser::target_interface:
|
||||
add_command = "add_library";
|
||||
target_type = "INTERFACE";
|
||||
target_type_string = "INTERFACE";
|
||||
target_scope = "INTERFACE";
|
||||
break;
|
||||
case parser::target_custom:
|
||||
// TODO: add proper support, this is hacky
|
||||
add_command = "add_custom_target";
|
||||
target_type = "SOURCES";
|
||||
target_type_string = "SOURCES";
|
||||
target_scope = "PUBLIC";
|
||||
break;
|
||||
case parser::target_object:
|
||||
// NOTE: This is properly supported since 3.12
|
||||
add_command = "add_library";
|
||||
target_type = "OBJECT";
|
||||
target_type_string = "OBJECT";
|
||||
target_scope = "PUBLIC";
|
||||
break;
|
||||
default:
|
||||
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
|
||||
cmd("if")(sources_var);
|
||||
cmd("target_sources")(target.name, target.type == parser::target_interface ? "INTERFACE" : "PRIVATE", "${" + sources_var + "}");
|
||||
cmd("endif")().endl();
|
||||
// clang-format on
|
||||
if (tmplate->pass_sources_to_add_function) {
|
||||
cmd(add_command)(target.name, target_type_string, "${" + sources_var + "}");
|
||||
} else {
|
||||
cmd(add_command)(target.name, target_type_string).endl();
|
||||
|
||||
// clang-format off
|
||||
cmd("if")(sources_var);
|
||||
cmd("target_sources")(target.name, target_type == parser::target_interface ? "INTERFACE" : "PRIVATE", "${" + sources_var + "}");
|
||||
cmd("endif")().endl();
|
||||
// clang-format on
|
||||
}
|
||||
} 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
|
||||
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");
|
||||
// clang-format off
|
||||
cmd("if")("NOT", "CMKR_VS_STARTUP_PROJECT");
|
||||
@@ -841,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); });
|
||||
};
|
||||
|
||||
target_cmd("target_compile_definitions", target.compile_definitions, target_scope);
|
||||
target_cmd("target_compile_definitions", target.private_compile_definitions, "PRIVATE");
|
||||
auto gen_target_cmds = [&](const parser::Target &t) {
|
||||
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", target.private_compile_features, "PRIVATE");
|
||||
target_cmd("target_compile_features", t.compile_features, target_scope);
|
||||
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", target.private_compile_options, "PRIVATE");
|
||||
target_cmd("target_compile_options", t.compile_options, target_scope);
|
||||
target_cmd("target_compile_options", t.private_compile_options, "PRIVATE");
|
||||
|
||||
target_cmd("target_include_directories", target.include_directories, target_scope);
|
||||
target_cmd("target_include_directories", target.private_include_directories, "PRIVATE");
|
||||
cmkr::parser::ConditionVector include_directories;
|
||||
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_link_directories", target.private_link_directories, "PRIVATE");
|
||||
target_cmd("target_include_directories", include_directories, target_scope);
|
||||
target_cmd("target_include_directories", t.private_include_directories, "PRIVATE");
|
||||
|
||||
target_cmd("target_link_libraries", target.link_libraries, target_scope);
|
||||
target_cmd("target_link_libraries", target.private_link_libraries, "PRIVATE");
|
||||
target_cmd("target_link_directories", t.link_directories, target_scope);
|
||||
target_cmd("target_link_directories", t.private_link_directories, "PRIVATE");
|
||||
|
||||
target_cmd("target_link_options", target.link_options, target_scope);
|
||||
target_cmd("target_link_options", target.private_link_options, "PRIVATE");
|
||||
target_cmd("target_link_libraries", t.link_libraries, target_scope);
|
||||
target_cmd("target_link_libraries", t.private_link_libraries, "PRIVATE");
|
||||
|
||||
target_cmd("target_precompile_headers", target.precompile_headers, target_scope);
|
||||
target_cmd("target_precompile_headers", target.private_precompile_headers, "PRIVATE");
|
||||
target_cmd("target_link_options", t.link_options, target_scope);
|
||||
target_cmd("target_link_options", t.private_link_options, "PRIVATE");
|
||||
|
||||
if (!target.properties.empty()) {
|
||||
gen.handle_condition(target.properties, [&](const std::string &, const tsl::ordered_map<std::string, std::string> &properties) {
|
||||
target_cmd("target_precompile_headers", t.precompile_headers, target_scope);
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
gen.handle_condition(target.include_after,
|
||||
[&](const std::string &, const std::vector<std::string> &includes) { inject_includes(includes); });
|
||||
gen.handle_condition(target.cmake_after, [&](const std::string &, const std::string &cmake) { inject_cmake(cmake); });
|
||||
gen.conditional_includes(target.include_after);
|
||||
gen.conditional_cmake(target.cmake_after);
|
||||
|
||||
if (tmplate != nullptr) {
|
||||
gen.conditional_includes(tmplate->outline.include_after);
|
||||
gen.conditional_cmake(tmplate->outline.cmake_after);
|
||||
}
|
||||
|
||||
cmd("unset")("CMKR_TARGET");
|
||||
cmd("unset")("CMKR_SOURCES");
|
||||
|
||||
if (!target.condition.empty()) {
|
||||
cmd("endif")();
|
||||
}
|
||||
|
||||
endl();
|
||||
}
|
||||
|
||||
endl();
|
||||
}
|
||||
|
||||
if (!project.tests.empty()) {
|
||||
@@ -898,6 +1042,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
auto working_directory = std::make_pair("WORKING_DIRECTORY", dir);
|
||||
auto command = std::make_pair("COMMAND", test.command);
|
||||
auto arguments = std::make_pair("", test.arguments);
|
||||
ConditionScope cs(gen, test.condition);
|
||||
cmd("add_test")(name, configurations, working_directory, command, arguments).endl();
|
||||
}
|
||||
}
|
||||
@@ -908,7 +1053,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
auto dirs = std::make_pair("DIRS", inst.dirs);
|
||||
std::vector<std::string> files_data;
|
||||
if (!inst.files.empty()) {
|
||||
files_data = expand_cmake_paths(inst.files, path);
|
||||
files_data = expand_cmake_paths(inst.files, path, root_project);
|
||||
if (files_data.empty()) {
|
||||
throw std::runtime_error("[[install]] files wildcard did not resolve to any files");
|
||||
}
|
||||
@@ -916,11 +1061,94 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
auto files = std::make_pair("FILES", inst.files);
|
||||
auto configs = std::make_pair("CONFIGURATIONS", inst.configs);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
auto list_path = fs::path(path) / "CMakeLists.txt";
|
||||
|
||||
|
||||
+301
-126
@@ -1,39 +1,28 @@
|
||||
#include "project_parser.hpp"
|
||||
|
||||
#include "enum_helper.hpp"
|
||||
#include "fs.hpp"
|
||||
#include <deque>
|
||||
#include <stdexcept>
|
||||
#include <toml.hpp>
|
||||
#include <tsl/ordered_map.h>
|
||||
|
||||
template <>
|
||||
const char *enumStrings<cmkr::parser::TargetType>::data[] = {"executable", "library", "shared", "static", "interface", "custom", "object"};
|
||||
#include <tsl/ordered_set.h>
|
||||
|
||||
namespace cmkr {
|
||||
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 EnumType to_enum(const std::string &str, const std::string &help_name) {
|
||||
EnumType value;
|
||||
try {
|
||||
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;
|
||||
static TargetType parse_targetType(const std::string &name) {
|
||||
for (int i = 0; i < target_last; i++) {
|
||||
if (name == targetTypeNames[i]) {
|
||||
return static_cast<TargetType>(i);
|
||||
}
|
||||
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) {
|
||||
auto loc = value.location();
|
||||
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;
|
||||
oss << "[error] " << error << '\n';
|
||||
oss << " --> " << loc.file_name() << '\n';
|
||||
oss << " --> " << loc.file_name() << ':' << loc.line() << '\n';
|
||||
|
||||
oss << std::string(line_width + 2, ' ') << "|\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) {
|
||||
oss << std::string(key_start + 1, ' ') << std::string(ky.length(), '~');
|
||||
}
|
||||
oss << '\n';
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
class TomlChecker {
|
||||
const TomlBasicValue &m_v;
|
||||
tsl::ordered_map<toml::key, bool> m_visited;
|
||||
tsl::ordered_map<toml::key, bool> m_conditionVisited;
|
||||
tsl::ordered_set<toml::key> m_visited;
|
||||
tsl::ordered_set<toml::key> m_conditionVisited;
|
||||
|
||||
public:
|
||||
TomlChecker(const TomlBasicValue &v, const toml::key &ky) : m_v(toml::find(v, ky)) {}
|
||||
@@ -72,7 +60,8 @@ class TomlChecker {
|
||||
TomlChecker(TomlChecker &&) = delete;
|
||||
|
||||
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
|
||||
const auto &table = m_v.as_table();
|
||||
for (const auto &itr : table) {
|
||||
@@ -81,28 +70,34 @@ class TomlChecker {
|
||||
if (value.is_table()) {
|
||||
if (value.contains(ky)) {
|
||||
destination[key] = toml::find<T>(value, ky);
|
||||
found = true;
|
||||
}
|
||||
} else if (key == ky) {
|
||||
destination[""] = toml::find<T>(m_v, ky);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle visiting logic
|
||||
for (const auto &itr : destination) {
|
||||
if (!itr.first.empty()) {
|
||||
m_conditionVisited.emplace(itr.first, true);
|
||||
m_conditionVisited.emplace(itr.first);
|
||||
}
|
||||
}
|
||||
visit(ky);
|
||||
return found;
|
||||
}
|
||||
|
||||
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>
|
||||
if (m_v.contains(ky)) {
|
||||
destination = toml::find<T>(m_v, ky);
|
||||
found = true;
|
||||
}
|
||||
visit(ky);
|
||||
return found;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
@@ -121,7 +116,9 @@ class TomlChecker {
|
||||
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 {
|
||||
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));
|
||||
} 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 {
|
||||
const TomlBasicValue &m_root;
|
||||
std::deque<TomlChecker> m_checkers;
|
||||
tsl::ordered_set<toml::key> m_visisted;
|
||||
bool m_checked = false;
|
||||
|
||||
public:
|
||||
TomlCheckerRoot() = default;
|
||||
TomlCheckerRoot(const TomlBasicValue &root) : m_root(root) {}
|
||||
TomlCheckerRoot(const 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) {
|
||||
m_checkers.emplace_back(v);
|
||||
return m_checkers.back();
|
||||
@@ -169,7 +178,14 @@ class TomlCheckerRoot {
|
||||
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) {
|
||||
checker.check(conditions);
|
||||
}
|
||||
@@ -179,13 +195,16 @@ class TomlCheckerRoot {
|
||||
Project::Project(const Project *parent, const std::string &path, bool build) {
|
||||
const auto toml_path = fs::path(path) / "cmake.toml";
|
||||
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());
|
||||
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");
|
||||
|
||||
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
|
||||
if (build) {
|
||||
checker.check(conditions);
|
||||
checker.check(conditions, false);
|
||||
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["clang"] = R"cmake(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_C_COMPILER_ID MATCHES "Clang")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 {
|
||||
conditions = parent->conditions;
|
||||
templates = parent->templates;
|
||||
}
|
||||
|
||||
if (toml.contains("conditions")) {
|
||||
if (checker.contains("conditions")) {
|
||||
auto conds = toml::find<decltype(conditions)>(toml, "conditions");
|
||||
for (const auto &cond : conds) {
|
||||
conditions[cond.first] = cond.second;
|
||||
}
|
||||
}
|
||||
|
||||
if (toml.contains("project")) {
|
||||
if (checker.contains("project")) {
|
||||
auto &project = checker.create(toml, "project");
|
||||
project.required("name", project_name);
|
||||
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);
|
||||
}
|
||||
|
||||
if (toml.contains("subdir")) {
|
||||
if (checker.contains("subdir")) {
|
||||
const auto &subs = toml::find(toml, "subdir").as_table();
|
||||
for (const auto &itr : subs) {
|
||||
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>;
|
||||
const auto &sets = toml::find<set_map>(toml, "settings");
|
||||
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>;
|
||||
const auto &opts = toml::find<opts_map>(toml, "options");
|
||||
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>;
|
||||
const auto &pkgs = toml::find<pkg_map>(toml, "find-package");
|
||||
for (const auto &itr : pkgs) {
|
||||
Package p;
|
||||
FindPackage p;
|
||||
p.name = itr.first;
|
||||
const auto &value = itr.second;
|
||||
if (itr.second.is_string()) {
|
||||
p.version = itr.second.as_string();
|
||||
} else {
|
||||
auto &pkg = checker.create(value);
|
||||
pkg.optional("condition", p.condition);
|
||||
pkg.optional("version", p.version);
|
||||
pkg.optional("required", p.required);
|
||||
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 (toml.contains("fetch-content")) {
|
||||
if (checker.contains("fetch-content")) {
|
||||
const auto &fc = toml::find(toml, "fetch-content").as_table();
|
||||
for (const auto &itr : fc) {
|
||||
Content content;
|
||||
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()) {
|
||||
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;
|
||||
if (key == "git") {
|
||||
key = "GIT_REPOSITORY";
|
||||
} else if (key == "tag") {
|
||||
key = "GIT_TAG";
|
||||
} else if (key == "shallow") {
|
||||
key = "GIT_SHALLOW";
|
||||
} else if (key == "svn") {
|
||||
key = "SVN_REPOSITORY";
|
||||
} else if (key == "rev") {
|
||||
key = "SVN_REVISION";
|
||||
} else if (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") {
|
||||
key = "URL_HASH";
|
||||
} else {
|
||||
// don't change arg
|
||||
} else if (is_cmake_arg(key)) {
|
||||
// allow passthrough of ExternalProject options
|
||||
} else if (!c.visisted(key)) {
|
||||
throw std::runtime_error(format_key_error("Unknown key '" + argItr.first + "'", argItr.first, argItr.second));
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
if (toml.contains("bin")) {
|
||||
if (checker.contains("bin")) {
|
||||
throw std::runtime_error("[[bin]] has been renamed to [[target]]");
|
||||
}
|
||||
|
||||
if (toml.contains("target")) {
|
||||
const auto &ts = toml::find(toml, "target").as_table();
|
||||
auto parse_target = [&](const std::string &name, TomlChecker &t, bool isTemplate) {
|
||||
Target target;
|
||||
target.name = name;
|
||||
|
||||
for (const auto &itr : ts) {
|
||||
const auto &value = itr.second;
|
||||
t.required("type", target.type_name);
|
||||
target.type = parse_targetType(target.type_name);
|
||||
|
||||
Target target;
|
||||
target.name = itr.first;
|
||||
// Users cannot set this target type
|
||||
if (target.type == target_template) {
|
||||
target.type = target_last;
|
||||
}
|
||||
|
||||
auto &t = checker.create(value);
|
||||
std::string type;
|
||||
t.required("type", type);
|
||||
target.type = to_enum<TargetType>(type, "target type");
|
||||
|
||||
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());
|
||||
if (!isTemplate && target.type == target_last) {
|
||||
for (const auto &tmplate : templates) {
|
||||
if (target.type_name == tmplate.outline.name) {
|
||||
target.type = target_template;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t.optional("condition", target.condition);
|
||||
t.optional("alias", target.alias);
|
||||
if (target.type == target_last) {
|
||||
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")) {
|
||||
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 += ';';
|
||||
}
|
||||
property_list += list_val.as_string();
|
||||
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);
|
||||
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;
|
||||
} else if (v.is_boolean()) {
|
||||
target.properties[condition][k] = v.as_boolean() ? "ON" : "OFF";
|
||||
} else {
|
||||
target.properties[condition][k] = v.as_string();
|
||||
property_list += list_val.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();
|
||||
for (const auto &propKv : props) {
|
||||
const auto &k = propKv.first;
|
||||
const auto &v = propKv.second;
|
||||
if (v.is_table()) {
|
||||
for (const auto &condKv : v.as_table()) {
|
||||
store_property(condKv.first, condKv.second, k);
|
||||
}
|
||||
} else {
|
||||
store_property(k, v, "");
|
||||
const auto &props = t.find("properties").as_table();
|
||||
for (const auto &propKv : props) {
|
||||
const auto &k = propKv.first;
|
||||
const auto &v = propKv.second;
|
||||
if (v.is_table()) {
|
||||
for (const auto &condKv : v.as_table()) {
|
||||
store_property(condKv.first, condKv.second, k);
|
||||
}
|
||||
} 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);
|
||||
t.optional("cmake-after", target.cmake_after);
|
||||
t.optional("include-before", target.include_before);
|
||||
t.optional("include-after", target.include_after);
|
||||
for (const auto &tmplate : templates) {
|
||||
if (name == tmplate.outline.name) {
|
||||
throw std::runtime_error(format_key_error("Template '" + name + "' already defined", name, itr.second));
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
for (const auto &value : ts) {
|
||||
auto &t = checker.create(value);
|
||||
Test test;
|
||||
t.required("name", test.name);
|
||||
t.optional("condition", test.condition);
|
||||
t.optional("configurations", test.configurations);
|
||||
t.optional("working-directory", test.working_directory);
|
||||
t.required("command", test.command);
|
||||
@@ -477,28 +614,66 @@ 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();
|
||||
for (const auto &value : is) {
|
||||
auto &i = checker.create(value);
|
||||
Install inst;
|
||||
i.optional("condition", inst.condition);
|
||||
i.optional("targets", inst.targets);
|
||||
i.optional("files", inst.files);
|
||||
i.optional("dirs", inst.dirs);
|
||||
i.optional("configs", inst.configs);
|
||||
i.required("destination", inst.destination);
|
||||
i.optional("component", inst.component);
|
||||
installs.push_back(inst);
|
||||
}
|
||||
}
|
||||
|
||||
if (toml.contains("vcpkg")) {
|
||||
if (checker.contains("vcpkg")) {
|
||||
auto &v = checker.create(toml, "vcpkg");
|
||||
v.optional("url", vcpkg.url);
|
||||
v.optional("version", vcpkg.version);
|
||||
v.required("packages", vcpkg.packages);
|
||||
for (const auto &p : v.find("packages").as_array()) {
|
||||
Vcpkg::Package package;
|
||||
const auto &package_str = p.as_string().str;
|
||||
const auto open_bracket = package_str.find('[');
|
||||
const auto close_bracket = package_str.find(']', open_bracket);
|
||||
if (open_bracket == std::string::npos && close_bracket == std::string::npos) {
|
||||
package.name = package_str;
|
||||
} else if (close_bracket != std::string::npos) {
|
||||
package.name = package_str.substr(0, open_bracket);
|
||||
const auto features = package_str.substr(open_bracket + 1, close_bracket - open_bracket - 1);
|
||||
std::istringstream feature_stream{features};
|
||||
std::string feature;
|
||||
while (std::getline(feature_stream, feature, ',')) {
|
||||
package.features.emplace_back(feature);
|
||||
}
|
||||
} else {
|
||||
throw std::runtime_error("Invalid vcpkg package '" + package_str + "'");
|
||||
}
|
||||
vcpkg.packages.emplace_back(std::move(package));
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
Generated
+10
@@ -78,3 +78,13 @@ add_test(
|
||||
build
|
||||
)
|
||||
|
||||
add_test(
|
||||
NAME
|
||||
templates
|
||||
WORKING_DIRECTORY
|
||||
"${CMAKE_CURRENT_LIST_DIR}/templates"
|
||||
COMMAND
|
||||
$<TARGET_FILE:cmkr>
|
||||
build
|
||||
)
|
||||
|
||||
|
||||
+19
-7
@@ -1,41 +1,53 @@
|
||||
[[test]]
|
||||
name = "basic"
|
||||
command = "$<TARGET_FILE:cmkr>"
|
||||
working-directory = "basic"
|
||||
command = "$<TARGET_FILE:cmkr>"
|
||||
arguments = ["build"]
|
||||
|
||||
[[test]]
|
||||
name = "interface"
|
||||
command = "$<TARGET_FILE:cmkr>"
|
||||
working-directory = "interface"
|
||||
command = "$<TARGET_FILE:cmkr>"
|
||||
arguments = ["build"]
|
||||
|
||||
[[test]]
|
||||
name = "fetch-content"
|
||||
command = "$<TARGET_FILE:cmkr>"
|
||||
working-directory = "fetch-content"
|
||||
command = "$<TARGET_FILE:cmkr>"
|
||||
arguments = ["build"]
|
||||
|
||||
[[test]]
|
||||
name = "conditions"
|
||||
command = "$<TARGET_FILE:cmkr>"
|
||||
working-directory = "conditions"
|
||||
command = "$<TARGET_FILE:cmkr>"
|
||||
arguments = ["build"]
|
||||
|
||||
[[test]]
|
||||
name = "vcpkg"
|
||||
command = "$<TARGET_FILE:cmkr>"
|
||||
working-directory = "vcpkg"
|
||||
command = "$<TARGET_FILE:cmkr>"
|
||||
arguments = ["build"]
|
||||
|
||||
[[test]]
|
||||
name = "cxx-standard"
|
||||
command = "$<TARGET_FILE:cmkr>"
|
||||
working-directory = "cxx-standard"
|
||||
command = "$<TARGET_FILE:cmkr>"
|
||||
arguments = ["build"]
|
||||
|
||||
[[test]]
|
||||
name = "globbing"
|
||||
command = "$<TARGET_FILE:cmkr>"
|
||||
working-directory = "globbing"
|
||||
command = "$<TARGET_FILE:cmkr>"
|
||||
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"]
|
||||
@@ -5,6 +5,7 @@ description = "Header-only library"
|
||||
[target.mylib]
|
||||
type = "interface"
|
||||
include-directories = ["include"]
|
||||
compile-features = ["cxx_std_11"]
|
||||
|
||||
[target.example]
|
||||
type = "executable"
|
||||
|
||||
@@ -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}::
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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
|
||||
}
|
||||
@@ -18,4 +18,5 @@ type = "executable"
|
||||
sources = ["src/main.cpp"]
|
||||
link-libraries = ["fmt::fmt"]
|
||||
|
||||
# The bootstrapping of vcpkg is fully automated and no user interaction is necessary. You can disable vcpkg by setting `CMKR_DISABLE_VCPKG=ON`.
|
||||
# The bootstrapping of vcpkg is fully automated and no user interaction is necessary. You can disable vcpkg by setting `CMKR_DISABLE_VCPKG=ON`.
|
||||
# To specify package features you can use the following syntax: `imgui[docking-experimental,freetype,sdl2-binding,opengl3-binding]`.
|
||||
Reference in New Issue
Block a user