mirror of
https://github.com/build-cpp/cmkr
synced 2026-06-08 13:22:55 +00:00
Compare commits
109 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| 04bf40a5a6 | |||
| 4d14395994 | |||
| 3dd3368e4e | |||
| bb7874a6df | |||
| 91dbf2986e | |||
| 2bcf15c4ed | |||
| 6a825e15a0 | |||
| cc2b984aa5 | |||
| 56a88c41e2 | |||
| 6809e8da41 | |||
| 9f9934e9a5 | |||
| 77f4bf7da0 | |||
| 09c9c28934 | |||
| f02ccc2309 | |||
| 37e9a1f1a0 | |||
| a28e63fddc | |||
| 156fc961ec | |||
| 31cc1c9481 | |||
| 19101cd726 | |||
| 8ebf69da7e | |||
| 1c23469c8c | |||
| 52671dcf5b | |||
| 094bab1b81 | |||
| 0f559bc803 | |||
| 491486075d | |||
| ed84fc0251 | |||
| 8b6780fe1a | |||
| 9ebcb1fb06 | |||
| 7c7144b183 | |||
| 07797988b5 | |||
| 285614e4c2 | |||
| e725b10a9f | |||
| aafaf58c70 | |||
| 87db22f134 | |||
| 416a8365f9 | |||
| a718dfd675 | |||
| 25da3171f0 | |||
| 95907602b4 | |||
| 4b300df8d5 | |||
| 760b2a8511 | |||
| fb9af0213b | |||
| 264e4ace18 | |||
| b6ab584a1a | |||
| 3cd84a9708 | |||
| 9932e501ce | |||
| 1b37dd76a5 | |||
| 82f2a11311 | |||
| eaf03eb785 | |||
| 8ea5b77ba5 | |||
| a12b3ae021 | |||
| 2450cfb2c9 | |||
| 8d024556e3 | |||
| af3807ca2b | |||
| 17f085c66d | |||
| 5ad6134e07 | |||
| 7b20d8c54c | |||
| ffdedbdace | |||
| bb7a0bc0ed | |||
| dd0003f8fe | |||
| a32caca8f0 | |||
| f565ad2af0 | |||
| dc4b59f05d | |||
| 4988f141e4 | |||
| 64b58425e6 | |||
| d4f0cdf152 | |||
| 306bb3d4fc | |||
| acd3688d16 | |||
| 23c5713c6b | |||
| cebe73c8e5 | |||
| 779d4ae96a | |||
| 7eed60bc7d | |||
| aa468dcf9c |
@@ -0,0 +1 @@
|
||||
github: [mrexodia]
|
||||
@@ -5,33 +5,37 @@ on: [push, pull_request]
|
||||
jobs:
|
||||
build:
|
||||
# Skip building pull requests from the same repository
|
||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != '${{ github.repository }}'
|
||||
if: ${{ github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-2016, windows-2019, macos-10.15, ubuntu-16.04, ubuntu-18.04, ubuntu-20.04]
|
||||
os: [windows-2019, macos-10.15, ubuntu-20.04]
|
||||
env:
|
||||
BUILD_TYPE: Release
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Tag cmkr.cmake
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
run: cmake -P "cmake/replace_tag.cmake"
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }}
|
||||
cmake -B build -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} -DCMAKE_UNITY_BUILD=ON
|
||||
cmake --build build --config ${{ env.BUILD_TYPE }} --parallel
|
||||
cmake --install build --prefix ./install --config ${{ env.BUILD_TYPE }}
|
||||
|
||||
- name: Check if cmkr was run
|
||||
run: |
|
||||
./install/bin/cmkr gen
|
||||
git diff --exit-code
|
||||
git diff --exit-code -- . ":(exclude)cmake/cmkr.cmake"
|
||||
|
||||
- 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
|
||||
@@ -46,16 +50,16 @@ 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
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
with:
|
||||
prerelease: ${{ !startsWith(github.ref, 'refs/tags/v') || contains(github.ref, '-pre') }}
|
||||
files: ${{ github.event.repository.name }}-${{ steps.osname.outputs.lowercase }}.zip
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
Generated
+36
-14
@@ -3,17 +3,15 @@
|
||||
|
||||
cmake_minimum_required(VERSION 2.8...3.8)
|
||||
|
||||
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR)
|
||||
message(FATAL_ERROR "In-tree builds are not supported. Run CMake from a separate directory: cmake -B build")
|
||||
endif()
|
||||
|
||||
# Regenerate CMakeLists.txt automatically in the root project
|
||||
set(CMKR_ROOT_PROJECT OFF)
|
||||
if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
|
||||
set(CMKR_ROOT_PROJECT ON)
|
||||
|
||||
# Bootstrap cmkr
|
||||
include(cmkr.cmake OPTIONAL RESULT_VARIABLE CMKR_INCLUDE_RESULT)
|
||||
if(CMKR_INCLUDE_RESULT)
|
||||
cmkr()
|
||||
endif()
|
||||
|
||||
# Enable folder support
|
||||
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
|
||||
endif()
|
||||
@@ -27,11 +25,14 @@ project(cmkr
|
||||
LANGUAGES
|
||||
CXX
|
||||
VERSION
|
||||
0.1.4
|
||||
0.2.6
|
||||
DESCRIPTION
|
||||
"CMakeLists generator from TOML"
|
||||
)
|
||||
|
||||
include("cmake/generate_documentation.cmake")
|
||||
include("cmake/generate_resources.cmake")
|
||||
|
||||
# third_party
|
||||
set(CMKR_CMAKE_FOLDER ${CMAKE_FOLDER})
|
||||
if(CMAKE_FOLDER)
|
||||
@@ -54,27 +55,44 @@ set(CMAKE_FOLDER ${CMKR_CMAKE_FOLDER})
|
||||
|
||||
|
||||
|
||||
# Target cmkr_generate_documentation
|
||||
set(CMKR_TARGET cmkr_generate_documentation)
|
||||
set(cmkr_generate_documentation_SOURCES "")
|
||||
|
||||
set(CMKR_SOURCES ${cmkr_generate_documentation_SOURCES})
|
||||
add_library(cmkr_generate_documentation INTERFACE)
|
||||
|
||||
if(cmkr_generate_documentation_SOURCES)
|
||||
target_sources(cmkr_generate_documentation INTERFACE ${cmkr_generate_documentation_SOURCES})
|
||||
endif()
|
||||
|
||||
generate_documentation()
|
||||
|
||||
unset(CMKR_TARGET)
|
||||
unset(CMKR_SOURCES)
|
||||
|
||||
# Target cmkr
|
||||
set(CMKR_TARGET cmkr)
|
||||
set(cmkr_SOURCES "")
|
||||
|
||||
list(APPEND cmkr_SOURCES
|
||||
"src/args.cpp"
|
||||
"src/arguments.cpp"
|
||||
"src/build.cpp"
|
||||
"src/cmake.cpp"
|
||||
"src/cmake_generator.cpp"
|
||||
"src/error.cpp"
|
||||
"src/gen.cpp"
|
||||
"src/help.cpp"
|
||||
"src/main.cpp"
|
||||
"include/args.hpp"
|
||||
"src/project_parser.cpp"
|
||||
"include/arguments.hpp"
|
||||
"include/build.hpp"
|
||||
"include/cmake.hpp"
|
||||
"include/enum_helper.hpp"
|
||||
"include/cmake_generator.hpp"
|
||||
"include/error.hpp"
|
||||
"include/fs.hpp"
|
||||
"include/gen.hpp"
|
||||
"include/help.hpp"
|
||||
"include/literals.hpp"
|
||||
"include/project_parser.hpp"
|
||||
"cmake/cmkr.cmake"
|
||||
"cmake/version.hpp.in"
|
||||
)
|
||||
|
||||
list(APPEND cmkr_SOURCES
|
||||
@@ -108,10 +126,14 @@ target_link_libraries(cmkr PRIVATE
|
||||
ghc_filesystem
|
||||
mpark_variant
|
||||
ordered_map
|
||||
nlohmann_json
|
||||
)
|
||||
|
||||
generate_resources(${CMKR_TARGET})
|
||||
|
||||
unset(CMKR_TARGET)
|
||||
unset(CMKR_SOURCES)
|
||||
|
||||
install(
|
||||
TARGETS
|
||||
cmkr
|
||||
|
||||
@@ -1,37 +1,54 @@
|
||||
# cmkr
|
||||
|
||||
`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).
|
||||
|
||||
**NOTE**: The documentation is currently a work-in-progress due to breaking changes since `0.1.4`. For examples you can check the [cmkr GitHub topic](https://github.com/topics/cmkr) and the [tests](https://github.com/build-cpp/cmkr/tree/main/tests).
|
||||
`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
|
||||
|
||||
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) or the [build-cpp organization](https://github.com/build-cpp) for more examples and templates.
|
||||
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]
|
||||
@@ -51,4 +68,4 @@ arguments:
|
||||
- https://github.com/Tessil/ordered-map
|
||||
- https://github.com/ToruNiina/toml11
|
||||
- https://github.com/mpark/variant
|
||||
- https://www.svgrepo.com/svg/192268/hammer
|
||||
- https://www.svgrepo.com/svg/192268/hammer
|
||||
|
||||
+31
-4
@@ -1,19 +1,46 @@
|
||||
[cmake]
|
||||
version = "2.8...3.8"
|
||||
cmkr-include = false
|
||||
|
||||
[project]
|
||||
name = "cmkr"
|
||||
version = "0.1.4"
|
||||
version = "0.2.6"
|
||||
description = "CMakeLists generator from TOML"
|
||||
languages = ["CXX"]
|
||||
subdirs = ["third_party", "tests"]
|
||||
include-after = [
|
||||
"cmake/generate_documentation.cmake",
|
||||
"cmake/generate_resources.cmake"
|
||||
]
|
||||
|
||||
[target.cmkr_generate_documentation]
|
||||
type = "interface"
|
||||
cmake-after = """
|
||||
generate_documentation()
|
||||
"""
|
||||
|
||||
[target.cmkr]
|
||||
type = "executable"
|
||||
sources = ["src/*.cpp", "include/*.hpp"]
|
||||
include-directories = ["include"]
|
||||
sources = [
|
||||
"src/*.cpp",
|
||||
"include/*.hpp",
|
||||
"cmake/cmkr.cmake",
|
||||
"cmake/version.hpp.in",
|
||||
]
|
||||
include-directories = [
|
||||
"include",
|
||||
]
|
||||
compile-features = ["cxx_std_11"]
|
||||
link-libraries = ["toml11", "ghc_filesystem", "mpark_variant", "ordered_map"]
|
||||
link-libraries = [
|
||||
"toml11",
|
||||
"ghc_filesystem",
|
||||
"mpark_variant",
|
||||
"ordered_map",
|
||||
"nlohmann_json",
|
||||
]
|
||||
cmake-after = """
|
||||
generate_resources(${CMKR_TARGET})
|
||||
"""
|
||||
|
||||
[[install]]
|
||||
targets = ["cmkr"]
|
||||
|
||||
Generated
-44
@@ -1,44 +0,0 @@
|
||||
# This file was generated automatically by cmkr.
|
||||
|
||||
cmake_minimum_required(VERSION 3.15)
|
||||
|
||||
# Regenerate CMakeLists.txt automatically in the root project
|
||||
set(CMKR_ROOT_PROJECT OFF)
|
||||
if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
|
||||
set(CMKR_ROOT_PROJECT ON)
|
||||
|
||||
# Bootstrap cmkr
|
||||
include(cmkr.cmake OPTIONAL RESULT_VARIABLE CMKR_INCLUDE_RESULT)
|
||||
if(CMKR_INCLUDE_RESULT)
|
||||
cmkr()
|
||||
endif()
|
||||
|
||||
# Enable folder support
|
||||
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
|
||||
endif()
|
||||
|
||||
# Create a configure-time dependency on cmake.toml to improve IDE support
|
||||
if(CMKR_ROOT_PROJECT)
|
||||
configure_file(cmake.toml cmake.toml COPYONLY)
|
||||
endif()
|
||||
|
||||
project(example
|
||||
VERSION
|
||||
0.1.0
|
||||
)
|
||||
|
||||
# Target example
|
||||
set(example_SOURCES
|
||||
"src/example.cpp"
|
||||
cmake.toml
|
||||
)
|
||||
|
||||
add_executable(example ${example_SOURCES})
|
||||
|
||||
get_directory_property(CMKR_VS_STARTUP_PROJECT DIRECTORY ${PROJECT_SOURCE_DIR} DEFINITION VS_STARTUP_PROJECT)
|
||||
if(NOT CMKR_VS_STARTUP_PROJECT)
|
||||
set_property(DIRECTORY ${PROJECT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT example)
|
||||
endif()
|
||||
|
||||
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${example_SOURCES})
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
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()
|
||||
|
||||
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" 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}")
|
||||
@@ -1,12 +0,0 @@
|
||||
# This is a minimal example project used for testing the cmkr bootstrapping process
|
||||
|
||||
[cmake]
|
||||
version = "3.15"
|
||||
|
||||
[project]
|
||||
name = "example"
|
||||
version = "0.1.0"
|
||||
|
||||
[target.example]
|
||||
type = "executable"
|
||||
sources = ["src/example.cpp"]
|
||||
+222
-156
@@ -1,156 +1,222 @@
|
||||
include_guard()
|
||||
|
||||
# Change these defaults to point to your infrastructure if desired
|
||||
set(CMKR_REPO "https://github.com/build-cpp/cmkr" CACHE STRING "cmkr git repository" FORCE)
|
||||
set(CMKR_TAG "archive_7cdf36f3" CACHE STRING "cmkr git tag (this needs to be available forever)" FORCE)
|
||||
|
||||
# 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")
|
||||
|
||||
# Disable cmkr if generation is disabled
|
||||
if(CMKR_SKIP_GENERATION)
|
||||
message(STATUS "[cmkr] Skipping automatic cmkr generation")
|
||||
macro(cmkr)
|
||||
endmacro()
|
||||
return()
|
||||
endif()
|
||||
|
||||
# Disable cmkr if no cmake.toml file is found
|
||||
if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake.toml")
|
||||
message(AUTHOR_WARNING "[cmkr] Not found: ${CMAKE_CURRENT_SOURCE_DIR}/cmake.toml")
|
||||
macro(cmkr)
|
||||
endmacro()
|
||||
return()
|
||||
endif()
|
||||
|
||||
# Convert a Windows native path to CMake path
|
||||
if(CMKR_EXECUTABLE MATCHES "\\\\")
|
||||
string(REPLACE "\\" "/" CMKR_EXECUTABLE_CMAKE "${CMKR_EXECUTABLE}")
|
||||
set(CMKR_EXECUTABLE "${CMKR_EXECUTABLE_CMAKE}" CACHE FILEPATH "" FORCE)
|
||||
unset(CMKR_EXECUTABLE_CMAKE)
|
||||
endif()
|
||||
|
||||
# Helper macro to execute a process (COMMAND_ERROR_IS_FATAL ANY is 3.19 and higher)
|
||||
function(cmkr_exec)
|
||||
execute_process(COMMAND ${ARGV} RESULT_VARIABLE CMKR_EXEC_RESULT)
|
||||
if(NOT CMKR_EXEC_RESULT EQUAL 0)
|
||||
message(FATAL_ERROR "cmkr_exec(${ARGV}) failed (exit code ${CMKR_EXEC_RESULT})")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# Windows-specific hack (CMAKE_EXECUTABLE_PREFIX is not set at the moment)
|
||||
if(WIN32)
|
||||
set(CMKR_EXECUTABLE_NAME "cmkr.exe")
|
||||
else()
|
||||
set(CMKR_EXECUTABLE_NAME "cmkr")
|
||||
endif()
|
||||
|
||||
# Use cached cmkr if found
|
||||
set(CMKR_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/_cmkr_${CMKR_TAG}")
|
||||
set(CMKR_CACHED_EXECUTABLE "${CMKR_DIRECTORY}/bin/${CMKR_EXECUTABLE_NAME}")
|
||||
|
||||
if(NOT CMKR_CACHED_EXECUTABLE STREQUAL CMKR_EXECUTABLE AND CMKR_EXECUTABLE MATCHES "^${CMAKE_CURRENT_BINARY_DIR}/_cmkr")
|
||||
message(AUTHOR_WARNING "[cmkr] Upgrading '${CMKR_EXECUTABLE}' to '${CMKR_CACHED_EXECUTABLE}'")
|
||||
unset(CMKR_EXECUTABLE CACHE)
|
||||
endif()
|
||||
|
||||
if(CMKR_EXECUTABLE AND EXISTS "${CMKR_EXECUTABLE}")
|
||||
message(VERBOSE "[cmkr] Found cmkr: '${CMKR_EXECUTABLE}'")
|
||||
elseif(CMKR_EXECUTABLE AND NOT CMKR_EXECUTABLE STREQUAL CMKR_CACHED_EXECUTABLE)
|
||||
message(FATAL_ERROR "[cmkr] '${CMKR_EXECUTABLE}' not found")
|
||||
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}")
|
||||
endif()
|
||||
find_package(Git QUIET REQUIRED)
|
||||
cmkr_exec("${GIT_EXECUTABLE}"
|
||||
clone
|
||||
--config advice.detachedHead=false
|
||||
--branch ${CMKR_TAG}
|
||||
--depth 1
|
||||
${CMKR_REPO}
|
||||
"${CMKR_DIRECTORY}"
|
||||
)
|
||||
message(STATUS "[cmkr] Building cmkr...")
|
||||
cmkr_exec("${CMAKE_COMMAND}"
|
||||
--no-warn-unused-cli
|
||||
"${CMKR_DIRECTORY}"
|
||||
"-B${CMKR_DIRECTORY}/build"
|
||||
"-DCMAKE_BUILD_TYPE=Release"
|
||||
"-DCMAKE_INSTALL_PREFIX=${CMKR_DIRECTORY}"
|
||||
)
|
||||
cmkr_exec("${CMAKE_COMMAND}"
|
||||
--build "${CMKR_DIRECTORY}/build"
|
||||
--config Release
|
||||
--parallel
|
||||
)
|
||||
cmkr_exec("${CMAKE_COMMAND}"
|
||||
--install "${CMKR_DIRECTORY}/build"
|
||||
--config Release
|
||||
--prefix "${CMKR_DIRECTORY}"
|
||||
--component cmkr
|
||||
)
|
||||
if(NOT EXISTS ${CMKR_EXECUTABLE})
|
||||
message(FATAL_ERROR "[cmkr] Failed to bootstrap '${CMKR_EXECUTABLE}'")
|
||||
endif()
|
||||
cmkr_exec("${CMKR_EXECUTABLE}" version)
|
||||
message(STATUS "[cmkr] Bootstrapped ${CMKR_EXECUTABLE}")
|
||||
endif()
|
||||
execute_process(COMMAND "${CMKR_EXECUTABLE}" version
|
||||
RESULT_VARIABLE CMKR_EXEC_RESULT
|
||||
)
|
||||
if(NOT CMKR_EXEC_RESULT EQUAL 0)
|
||||
message(FATAL_ERROR "[cmkr] Failed to get version, try clearing the cache and rebuilding")
|
||||
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
|
||||
get_source_file_property(CMKR_CURRENT_LIST_FILE "${CMAKE_CURRENT_LIST_FILE}" CMKR_CURRENT_LIST_FILE)
|
||||
if(CMKR_CURRENT_LIST_FILE)
|
||||
set(CMAKE_CURRENT_LIST_FILE "${CMKR_CURRENT_LIST_FILE}")
|
||||
get_filename_component(CMAKE_CURRENT_LIST_DIR "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY)
|
||||
endif()
|
||||
|
||||
# File-based include guard (include_guard is not documented to work)
|
||||
get_source_file_property(CMKR_INCLUDE_GUARD "${CMAKE_CURRENT_LIST_FILE}" CMKR_INCLUDE_GUARD)
|
||||
if(NOT CMKR_INCLUDE_GUARD)
|
||||
set_source_files_properties("${CMAKE_CURRENT_LIST_FILE}" PROPERTIES CMKR_INCLUDE_GUARD TRUE)
|
||||
|
||||
file(SHA256 "${CMAKE_CURRENT_LIST_FILE}" CMKR_LIST_FILE_SHA256_PRE)
|
||||
|
||||
# Generate CMakeLists.txt
|
||||
cmkr_exec("${CMKR_EXECUTABLE}" gen
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
)
|
||||
|
||||
file(SHA256 "${CMAKE_CURRENT_LIST_FILE}" CMKR_LIST_FILE_SHA256_POST)
|
||||
|
||||
if(NOT CMKR_LIST_FILE_SHA256_PRE STREQUAL CMKR_LIST_FILE_SHA256_POST)
|
||||
# Copy the now-generated CMakeLists.txt to CMakerLists.txt
|
||||
# This is done because you cannot include() a file you are currently in
|
||||
set(CMKR_TEMP_FILE "${CMAKE_CURRENT_SOURCE_DIR}/CMakerLists.txt")
|
||||
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()
|
||||
# Resume executing the unmodified CMakeLists.txt
|
||||
endif()
|
||||
endmacro()
|
||||
include_guard()
|
||||
|
||||
# Change these defaults to point to your infrastructure if desired
|
||||
set(CMKR_REPO "https://github.com/build-cpp/cmkr" CACHE STRING "cmkr git repository" FORCE)
|
||||
set(CMKR_TAG "v0.2.6" CACHE STRING "cmkr git tag (this needs to be available forever)" 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")
|
||||
|
||||
# Disable cmkr if generation is disabled
|
||||
if(DEFINED ENV{CI} OR CMKR_SKIP_GENERATION OR CMKR_BUILD_SKIP_GENERATION)
|
||||
message(STATUS "[cmkr] Skipping automatic cmkr generation")
|
||||
unset(CMKR_BUILD_SKIP_GENERATION CACHE)
|
||||
macro(cmkr)
|
||||
endmacro()
|
||||
return()
|
||||
endif()
|
||||
|
||||
# Disable cmkr if no cmake.toml file is found
|
||||
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()
|
||||
return()
|
||||
endif()
|
||||
|
||||
# Convert a Windows native path to CMake path
|
||||
if(CMKR_EXECUTABLE MATCHES "\\\\")
|
||||
string(REPLACE "\\" "/" CMKR_EXECUTABLE_CMAKE "${CMKR_EXECUTABLE}")
|
||||
set(CMKR_EXECUTABLE "${CMKR_EXECUTABLE_CMAKE}" CACHE FILEPATH "" FORCE)
|
||||
unset(CMKR_EXECUTABLE_CMAKE)
|
||||
endif()
|
||||
|
||||
# Helper macro to execute a process (COMMAND_ERROR_IS_FATAL ANY is 3.19 and higher)
|
||||
function(cmkr_exec)
|
||||
execute_process(COMMAND ${ARGV} RESULT_VARIABLE CMKR_EXEC_RESULT)
|
||||
if(NOT CMKR_EXEC_RESULT EQUAL 0)
|
||||
message(FATAL_ERROR "cmkr_exec(${ARGV}) failed (exit code ${CMKR_EXEC_RESULT})")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# Windows-specific hack (CMAKE_EXECUTABLE_PREFIX is not set at the moment)
|
||||
if(WIN32)
|
||||
set(CMKR_EXECUTABLE_NAME "cmkr.exe")
|
||||
else()
|
||||
set(CMKR_EXECUTABLE_NAME "cmkr")
|
||||
endif()
|
||||
|
||||
# Use cached cmkr if found
|
||||
if(DEFINED ENV{CMKR_CACHE} AND EXISTS "$ENV{CMKR_CACHE}")
|
||||
set(CMKR_DIRECTORY_PREFIX "$ENV{CMKR_CACHE}")
|
||||
string(REPLACE "\\" "/" CMKR_DIRECTORY_PREFIX "${CMKR_DIRECTORY_PREFIX}")
|
||||
if(NOT CMKR_DIRECTORY_PREFIX MATCHES "\\/$")
|
||||
set(CMKR_DIRECTORY_PREFIX "${CMKR_DIRECTORY_PREFIX}/")
|
||||
endif()
|
||||
else()
|
||||
set(CMKR_DIRECTORY_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/_cmkr_")
|
||||
endif()
|
||||
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 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}'")
|
||||
if(EXISTS "${CMKR_CACHED_EXECUTABLE}")
|
||||
set(CMKR_EXECUTABLE "${CMKR_CACHED_EXECUTABLE}" CACHE FILEPATH "Full path to cmkr executable" FORCE)
|
||||
else()
|
||||
unset(CMKR_EXECUTABLE CACHE)
|
||||
endif()
|
||||
else()
|
||||
message(AUTHOR_WARNING "[cmkr] Upgrading '${CMKR_EXECUTABLE}' to '${CMKR_CACHED_EXECUTABLE}'")
|
||||
unset(CMKR_EXECUTABLE CACHE)
|
||||
endif()
|
||||
elseif(DEFINED ENV{CMKR_CACHE} AND EXISTS "$ENV{CMKR_CACHE}" AND CMKR_EXECUTABLE MATCHES "^${CMKR_DIRECTORY_PREFIX}")
|
||||
message(AUTHOR_WARNING "[cmkr] Upgrading cached '${CMKR_EXECUTABLE}' to '${CMKR_CACHED_EXECUTABLE}'")
|
||||
unset(CMKR_EXECUTABLE CACHE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(CMKR_EXECUTABLE AND EXISTS "${CMKR_EXECUTABLE}")
|
||||
message(VERBOSE "[cmkr] Found cmkr: '${CMKR_EXECUTABLE}'")
|
||||
elseif(CMKR_EXECUTABLE AND NOT CMKR_EXECUTABLE STREQUAL CMKR_CACHED_EXECUTABLE)
|
||||
message(FATAL_ERROR "[cmkr] '${CMKR_EXECUTABLE}' not found")
|
||||
elseif(NOT CMKR_EXECUTABLE AND EXISTS "${CMKR_CACHED_EXECUTABLE}")
|
||||
set(CMKR_EXECUTABLE "${CMKR_CACHED_EXECUTABLE}" CACHE FILEPATH "Full path to cmkr executable" FORCE)
|
||||
message(STATUS "[cmkr] Found cached cmkr: '${CMKR_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}")
|
||||
endif()
|
||||
find_package(Git QUIET REQUIRED)
|
||||
cmkr_exec("${GIT_EXECUTABLE}"
|
||||
clone
|
||||
--config advice.detachedHead=false
|
||||
--branch ${CMKR_TAG}
|
||||
--depth 1
|
||||
${CMKR_REPO}
|
||||
"${CMKR_DIRECTORY}"
|
||||
)
|
||||
message(STATUS "[cmkr] Building cmkr (using system compiler)...")
|
||||
cmkr_exec("${CMAKE_COMMAND}"
|
||||
--no-warn-unused-cli
|
||||
"${CMKR_DIRECTORY}"
|
||||
"-B${CMKR_DIRECTORY}/build"
|
||||
"-DCMAKE_BUILD_TYPE=${CMKR_BUILD_TYPE}"
|
||||
"-DCMAKE_UNITY_BUILD=ON"
|
||||
"-DCMAKE_INSTALL_PREFIX=${CMKR_DIRECTORY}"
|
||||
"-DCMKR_GENERATE_DOCUMENTATION=OFF"
|
||||
)
|
||||
cmkr_exec("${CMAKE_COMMAND}"
|
||||
--build "${CMKR_DIRECTORY}/build"
|
||||
--config "${CMKR_BUILD_TYPE}"
|
||||
--parallel
|
||||
)
|
||||
cmkr_exec("${CMAKE_COMMAND}"
|
||||
--install "${CMKR_DIRECTORY}/build"
|
||||
--config "${CMKR_BUILD_TYPE}"
|
||||
--prefix "${CMKR_DIRECTORY}"
|
||||
--component cmkr
|
||||
)
|
||||
if(NOT EXISTS ${CMKR_EXECUTABLE})
|
||||
message(FATAL_ERROR "[cmkr] Failed to bootstrap '${CMKR_EXECUTABLE}'")
|
||||
endif()
|
||||
cmkr_exec("${CMKR_EXECUTABLE}" version)
|
||||
message(STATUS "[cmkr] Bootstrapped ${CMKR_EXECUTABLE}")
|
||||
endif()
|
||||
execute_process(COMMAND "${CMKR_EXECUTABLE}" version
|
||||
RESULT_VARIABLE CMKR_EXEC_RESULT
|
||||
)
|
||||
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
|
||||
get_source_file_property(CMKR_CURRENT_LIST_FILE "${CMAKE_CURRENT_LIST_FILE}" CMKR_CURRENT_LIST_FILE)
|
||||
if(CMKR_CURRENT_LIST_FILE)
|
||||
set(CMAKE_CURRENT_LIST_FILE "${CMKR_CURRENT_LIST_FILE}")
|
||||
get_filename_component(CMAKE_CURRENT_LIST_DIR "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY)
|
||||
endif()
|
||||
|
||||
# File-based include guard (include_guard is not documented to work)
|
||||
get_source_file_property(CMKR_INCLUDE_GUARD "${CMAKE_CURRENT_LIST_FILE}" CMKR_INCLUDE_GUARD)
|
||||
if(NOT CMKR_INCLUDE_GUARD)
|
||||
set_source_files_properties("${CMAKE_CURRENT_LIST_FILE}" PROPERTIES CMKR_INCLUDE_GUARD TRUE)
|
||||
|
||||
file(SHA256 "${CMAKE_CURRENT_LIST_FILE}" CMKR_LIST_FILE_SHA256_PRE)
|
||||
|
||||
# Generate CMakeLists.txt
|
||||
cmkr_exec("${CMKR_EXECUTABLE}" gen
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
)
|
||||
|
||||
file(SHA256 "${CMAKE_CURRENT_LIST_FILE}" CMKR_LIST_FILE_SHA256_POST)
|
||||
|
||||
# Delete the temporary file if it was left for some reason
|
||||
set(CMKR_TEMP_FILE "${CMAKE_CURRENT_SOURCE_DIR}/CMakerLists.txt")
|
||||
if(EXISTS "${CMKR_TEMP_FILE}")
|
||||
file(REMOVE "${CMKR_TEMP_FILE}")
|
||||
endif()
|
||||
|
||||
if(NOT CMKR_LIST_FILE_SHA256_PRE STREQUAL CMKR_LIST_FILE_SHA256_POST)
|
||||
# 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()
|
||||
# Resume executing the unmodified CMakeLists.txt
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
# Automatically generated from tests/@EXAMPLE_PERMALINK@/cmake.toml - DO NOT EDIT
|
||||
layout: default
|
||||
title: @EXAMPLE_TITLE@
|
||||
permalink: /examples/@EXAMPLE_PERMALINK@
|
||||
parent: Examples
|
||||
nav_order: @EXAMPLE_INDEX@
|
||||
---
|
||||
|
||||
# @EXAMPLE_TITLE@
|
||||
|
||||
@EXAMPLE_HEADER@
|
||||
|
||||
```toml
|
||||
@EXAMPLE_TOML@
|
||||
```
|
||||
|
||||
@EXAMPLE_FOOTER@
|
||||
|
||||
<sup><sub>This page was automatically generated from [tests/@EXAMPLE_PERMALINK@/cmake.toml](https://github.com/build-cpp/cmkr/tree/main/tests/@EXAMPLE_PERMALINK@/cmake.toml).</sub></sup>
|
||||
@@ -0,0 +1,76 @@
|
||||
option(CMKR_GENERATE_DOCUMENTATION "Generate cmkr documentation" ${CMKR_ROOT_PROJECT})
|
||||
set(CMKR_TESTS "" CACHE INTERNAL "List of test directories in the order declared in tests/cmake.toml")
|
||||
|
||||
if(CMKR_GENERATE_DOCUMENTATION)
|
||||
# Hook the add_test function to capture the tests in the order declared in tests/cmake.toml
|
||||
function(add_test)
|
||||
cmake_parse_arguments(TEST "" "WORKING_DIRECTORY" "" ${ARGN})
|
||||
get_filename_component(TEST_WORKING_DIRECTORY "${TEST_WORKING_DIRECTORY}" NAME)
|
||||
list(APPEND CMKR_TESTS "${TEST_WORKING_DIRECTORY}")
|
||||
set(CMKR_TESTS "${CMKR_TESTS}" CACHE INTERNAL "")
|
||||
_add_test(${test} ${ARGN})
|
||||
endfunction()
|
||||
endif()
|
||||
|
||||
function(generate_documentation)
|
||||
if(CMKR_GENERATE_DOCUMENTATION)
|
||||
message(STATUS "[cmkr] Generating documentation...")
|
||||
|
||||
# Delete previously generated examples
|
||||
set(example_folder "${PROJECT_SOURCE_DIR}/docs/examples")
|
||||
file(GLOB example_files "${example_folder}/*.md")
|
||||
list(REMOVE_ITEM example_files "${example_folder}/index.md")
|
||||
if(example_files)
|
||||
file(REMOVE ${example_files})
|
||||
endif()
|
||||
|
||||
message(DEBUG "[cmkr] Test directories: ${CMKR_TESTS}")
|
||||
set(test_index 0)
|
||||
foreach(test_dir ${CMKR_TESTS})
|
||||
set(test_name "${test_dir}")
|
||||
set(test_dir "${PROJECT_SOURCE_DIR}/tests/${test_dir}")
|
||||
set(test_toml "${test_dir}/cmake.toml")
|
||||
if(IS_DIRECTORY "${test_dir}" AND EXISTS "${test_toml}")
|
||||
message(DEBUG "[cmkr] Generating documentation for: ${test_toml} (index: ${test_index})")
|
||||
|
||||
# Set template variables
|
||||
set(EXAMPLE_PERMALINK "${test_name}")
|
||||
set(EXAMPLE_INDEX ${test_index})
|
||||
math(EXPR test_index "${test_index}+1")
|
||||
|
||||
# Read cmake.toml file
|
||||
file(READ "${test_toml}" test_contents NO_HEX_CONVERSION)
|
||||
string(LENGTH "${test_contents}" toml_length)
|
||||
|
||||
# Extract header text
|
||||
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\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\n" EXAMPLE_FOOTER "\n${EXAMPLE_FOOTER}")
|
||||
string(STRIP "${EXAMPLE_FOOTER}" EXAMPLE_FOOTER)
|
||||
|
||||
# Extract toml body
|
||||
math(EXPR toml_length "${toml_length}-${header_length}-${footer_length}")
|
||||
string(SUBSTRING "${test_contents}" ${header_length} ${toml_length} EXAMPLE_TOML)
|
||||
string(STRIP "${EXAMPLE_TOML}" EXAMPLE_TOML)
|
||||
|
||||
# Extract title from description
|
||||
if("${EXAMPLE_TOML}" MATCHES "description *= *\"([^\"]+)\"")
|
||||
set(EXAMPLE_TITLE "${CMAKE_MATCH_1}")
|
||||
|
||||
# Generate documentation markdown page
|
||||
configure_file("${PROJECT_SOURCE_DIR}/cmake/example.md.in" "${example_folder}/${EXAMPLE_PERMALINK}.md" @ONLY NEWLINE_STYLE LF)
|
||||
else()
|
||||
message(DEBUG "[cmkr] Skipping documentation generation for ${test_name} because description is missing")
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
endfunction()
|
||||
@@ -0,0 +1,24 @@
|
||||
# Enumerates the target sources and automatically generates include/resources/${RESOURCE_NAME}.h from all .cmake files
|
||||
function(generate_resources target)
|
||||
get_property(TARGET_SOURCES
|
||||
TARGET ${target}
|
||||
PROPERTY SOURCES
|
||||
)
|
||||
foreach(SOURCE ${TARGET_SOURCES})
|
||||
get_filename_component(RESOURCE_NAME "${SOURCE}" NAME_WE)
|
||||
set(RESOURCE_HEADER "include/resources/${RESOURCE_NAME}.hpp")
|
||||
|
||||
if(SOURCE MATCHES ".cmake$")
|
||||
# Add configure-time dependency on the source file
|
||||
configure_file("${SOURCE}" "${RESOURCE_HEADER}" COPYONLY)
|
||||
# Generate the actual resource into the header
|
||||
file(READ "${SOURCE}" RESOURCE_CONTENTS)
|
||||
configure_file("${PROJECT_SOURCE_DIR}/cmake/resource.hpp.in" "${RESOURCE_HEADER}" @ONLY)
|
||||
message(STATUS "[cmkr] Generated ${RESOURCE_HEADER}")
|
||||
elseif(SOURCE MATCHES ".in$")
|
||||
configure_file("${SOURCE}" "${RESOURCE_HEADER}" @ONLY)
|
||||
message(STATUS "[cmkr] Generated ${RESOURCE_HEADER}")
|
||||
endif()
|
||||
endforeach()
|
||||
target_include_directories(${target} PUBLIC "${CMAKE_CURRENT_BINARY_DIR}/include")
|
||||
endfunction()
|
||||
@@ -0,0 +1,27 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
find_package(Git REQUIRED)
|
||||
|
||||
execute_process(COMMAND
|
||||
"${GIT_EXECUTABLE}" name-rev --tags --name-only HEAD
|
||||
OUTPUT_VARIABLE GIT_TAG
|
||||
)
|
||||
|
||||
string(FIND "${GIT_TAG}" "\n" NEWLINE_POS)
|
||||
string(SUBSTRING "${GIT_TAG}" 0 ${NEWLINE_POS} GIT_TAG)
|
||||
string(STRIP "${GIT_TAG}" GIT_TAG)
|
||||
|
||||
if("${GIT_TAG}" STREQUAL "")
|
||||
message(FATAL_ERROR "Failed to retrieve git tag!")
|
||||
endif()
|
||||
|
||||
message(STATUS "CMKR_TAG: '${GIT_TAG}'")
|
||||
|
||||
file(READ "cmake/cmkr.cmake" CMKR_CMAKE)
|
||||
string(REGEX REPLACE "CMKR_TAG \"[^\"]+\"" "CMKR_TAG \"${GIT_TAG}\"" CMKR_CMAKE "${CMKR_CMAKE}")
|
||||
file(CONFIGURE
|
||||
OUTPUT "cmake/cmkr.cmake"
|
||||
CONTENT "${CMKR_CMAKE}"
|
||||
@ONLY
|
||||
NEWLINE_STYLE LF
|
||||
)
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace cmkr {
|
||||
namespace resources {
|
||||
|
||||
static const char* @RESOURCE_NAME@ = R"RESOURCE(@RESOURCE_CONTENTS@)RESOURCE";
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
#include <cstdio>
|
||||
|
||||
int main()
|
||||
{
|
||||
puts("Hello from cmkr!");
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#define CMKR_VERSION "@PROJECT_VERSION@"
|
||||
+3
-1
@@ -1 +1,3 @@
|
||||
_site
|
||||
_site/
|
||||
.jekyll-metadata
|
||||
assets/
|
||||
|
||||
@@ -11,3 +11,5 @@ end
|
||||
|
||||
gem "wdm", "~> 0.1.1", :platforms => [:mingw, :x64_mingw, :mswin]
|
||||
gem "just-the-docs"
|
||||
|
||||
gem "webrick", "~> 1.7"
|
||||
|
||||
+3
-1
@@ -262,6 +262,7 @@ GEM
|
||||
unf_ext
|
||||
unf_ext (0.0.7.7)
|
||||
unicode-display_width (1.7.0)
|
||||
webrick (1.7.0)
|
||||
zeitwerk (2.4.2)
|
||||
|
||||
PLATFORMS
|
||||
@@ -275,6 +276,7 @@ DEPENDENCIES
|
||||
tzinfo (~> 1.2)
|
||||
tzinfo-data
|
||||
wdm (~> 0.1.1)
|
||||
webrick (~> 1.7)
|
||||
|
||||
BUNDLED WITH
|
||||
2.1.2
|
||||
2.2.26
|
||||
|
||||
+15
-3
@@ -3,7 +3,19 @@ title: cmkr
|
||||
plugins:
|
||||
- jekyll-remote-theme
|
||||
|
||||
# Build settings
|
||||
# theme: just-the-docs
|
||||
remote_theme: pmarsceill/just-the-docs
|
||||
# Automatically deduce dark theme preference https://github.com/pmarsceill/just-the-docs/pull/464
|
||||
remote_theme: build-cpp/just-the-docs@light-switch
|
||||
search_enabled: true
|
||||
color_scheme: light-or-dark
|
||||
heading_anchors: true
|
||||
aux_links:
|
||||
"cmkr":
|
||||
- "https://github.com/build-cpp/cmkr"
|
||||
aux_links_new_tab: true
|
||||
|
||||
gh_edit_link: true
|
||||
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
|
||||
@@ -0,0 +1,206 @@
|
||||
---
|
||||
layout: page
|
||||
title: Reference
|
||||
permalink: /cmake-toml/
|
||||
nav_order: 1
|
||||
---
|
||||
|
||||
# Reference
|
||||
|
||||
This page is a reference for the options in the `cmake.toml` file. If you think anything is missing or unclear, please [edit this page](https://github.com/build-cpp/cmkr/edit/main/docs/cmake-toml.md) or open an [issue](https://github.com/build-cpp/cmkr/issues).
|
||||
|
||||
See the [examples](/examples) section for concrete examples.
|
||||
|
||||
## CMake configuration
|
||||
|
||||
```toml
|
||||
[cmake]
|
||||
version = "3.15"
|
||||
cmkr-include = "cmkr.cmake"
|
||||
```
|
||||
|
||||
## Project configuration
|
||||
|
||||
```toml
|
||||
[project]
|
||||
name = "myproject"
|
||||
version = "1.0.0"
|
||||
description = "Description of the project"
|
||||
languages = ["C", "CXX"]
|
||||
cmake-before = """
|
||||
message(STATUS "CMake injected before the project() call")
|
||||
"""
|
||||
cmake-after = """
|
||||
message(STATUS "CMake injected after the project() call")
|
||||
"""
|
||||
include-before = ["cmake/before-project.cmake"]
|
||||
include-after = ["cmake/after-project.cmake"]
|
||||
```
|
||||
|
||||
## Conditions
|
||||
|
||||
You can specify your own conditions and use them in any `condition` field:
|
||||
|
||||
```toml
|
||||
[conditions]
|
||||
arch64 = "CMAKE_SIZEOF_VOID_P EQUALS 8"
|
||||
arch32 = "CMAKE_SIZEOF_VOID_P EQUALS 4"
|
||||
```
|
||||
|
||||
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
|
||||
[target]
|
||||
type = "executable"
|
||||
sources = ["src/main.cpp"]
|
||||
windows.sources = ["src/windows_specific.cpp"]
|
||||
```
|
||||
|
||||
The following conditions are predefined (you can override them if you desire):
|
||||
|
||||
```toml
|
||||
[conditions]
|
||||
windows = "WIN32"
|
||||
macos = "CMAKE_SYSTEM_NAME MATCHES \"Darwin\""
|
||||
unix = "UNIX"
|
||||
bsd = "CMAKE_SYSTEM_NAME MATCHES \"BSD\""
|
||||
linux = "CMAKE_SYSTEM_NAME MATCHES \"Linux\""
|
||||
gcc = "CMAKE_CXX_COMPILER_ID STREQUAL \"GNU\" OR CMAKE_C_COMPILER_ID STREQUAL \"GNU\""
|
||||
clang = "CMAKE_CXX_COMPILER_ID MATCHES \"Clang\" OR CMAKE_C_COMPILER_ID MATCHES \"Clang\""
|
||||
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 = "mycondition"
|
||||
cmake-before = """
|
||||
message(STATUS "CMake injected before the add_subdirectory() call"
|
||||
"""
|
||||
cmake-after = """
|
||||
message(STATUS "CMake injected after the add_subdirectory() call")
|
||||
"""
|
||||
include-before = ["cmake/before-subdir.cmake"]
|
||||
include-after = ["cmake/after-subdir.cmake"]
|
||||
```
|
||||
|
||||
## Vcpkg
|
||||
|
||||
```toml
|
||||
[vcpkg]
|
||||
version = "2021.05.12"
|
||||
url = "https://github.com/microsoft/vcpkg/archive/refs/tags/2021.05.12.tar.gz"
|
||||
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]
|
||||
condition = "mycondition"
|
||||
version = "1.0"
|
||||
required = true
|
||||
config = true
|
||||
components = ["mycomponent"]
|
||||
```
|
||||
|
||||
## FetchContent
|
||||
|
||||
**Note**: The `[fetch-content]` feature is unpolished and will likely change in a future release.
|
||||
|
||||
```toml
|
||||
[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 = "mycondition"
|
||||
alias = "mytarget::mytarget"
|
||||
type = "static" # executable, shared (DLL), static, interface, object, library, custom
|
||||
headers = ["src/mytarget.h"]
|
||||
sources = ["src/mytarget.cpp"]
|
||||
|
||||
# The keys below match the target_xxx CMake commands
|
||||
# Keys prefixed with private- will get PRIVATE visibility
|
||||
compile-definitions = [""]
|
||||
private-compile-definitions = [""]
|
||||
compile-features = [""]
|
||||
private-compile-features = [""]
|
||||
compile-options = [""]
|
||||
private-compile-options = [""]
|
||||
include-directories = [""]
|
||||
private-include-directories = [""]
|
||||
link-directories = [""]
|
||||
private-link-directories = [""]
|
||||
link-libraries = [""]
|
||||
private-link-libraries = [""]
|
||||
link-options = [""]
|
||||
private-link-options = [""]
|
||||
precompile-headers = [""]
|
||||
private-precompile-headers = [""]
|
||||
|
||||
cmake-before = """
|
||||
message(STATUS "CMake injected before the target")
|
||||
"""
|
||||
cmake-after = """
|
||||
message(STATUS "CMake injected after the target")
|
||||
"""
|
||||
include-before = "cmake/target-before.cmake"
|
||||
include-after = "cmake/target-after.cmake"
|
||||
|
||||
# See https://cmake.org/cmake/help/latest/manual/cmake-properties.7.html#properties-on-targets for a list of target properties
|
||||
[target.mytarget.properties]
|
||||
CXX_STANDARD = 17
|
||||
CXX_STANDARD_REQUIRED = true
|
||||
FOLDER = "MyFolder"
|
||||
```
|
||||
|
||||
## Tests and installation (unfinished)
|
||||
|
||||
**Note**: The `[[test]]` and `[[install]]` are unfinished features and will likely change in a future release.
|
||||
|
||||
```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"]
|
||||
configurations = ["Debug", "Release", "RelWithDebInfo", "MinSizeRelease"]
|
||||
working-directory = "mytest-dir"
|
||||
```
|
||||
|
||||
```toml
|
||||
[[install]]
|
||||
condition = "mycondition"
|
||||
targets = ["mytarget", "mytest"]
|
||||
destination = ["bin"]
|
||||
files = ["content/my.png"]
|
||||
dirs = [""]
|
||||
configs = [""]
|
||||
```
|
||||
@@ -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.
|
||||
```
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
# Automatically generated from tests/basic/cmake.toml - DO NOT EDIT
|
||||
layout: default
|
||||
title: Minimal example
|
||||
permalink: /examples/basic
|
||||
parent: Examples
|
||||
nav_order: 0
|
||||
---
|
||||
|
||||
# Minimal example
|
||||
|
||||
A minimal `cmake.toml` project:
|
||||
|
||||
```toml
|
||||
[project]
|
||||
name = "basic"
|
||||
description = "Minimal example"
|
||||
|
||||
[target.basic]
|
||||
type = "executable"
|
||||
sources = ["src/basic.cpp"]
|
||||
```
|
||||
|
||||
Declares an executable target called `basic` with `src/basic.cpp` as a source file. Equivalent to CMake's [add_executable](https://cmake.org/cmake/help/latest/command/add_executable.html)`(basic src/basic.cpp)`.
|
||||
|
||||
<sup><sub>This page was automatically generated from [tests/basic/cmake.toml](https://github.com/build-cpp/cmkr/tree/main/tests/basic/cmake.toml).</sub></sup>
|
||||
@@ -1,17 +0,0 @@
|
||||
---
|
||||
layout: default
|
||||
title: Changing C/C++ version
|
||||
permalink: /examples/cpp-version-change
|
||||
parent: Examples
|
||||
nav_order: 3
|
||||
---
|
||||
|
||||
# Changing C/C++ version
|
||||
|
||||
Simple example changing C++ to version 20 and C standard to the version 11
|
||||
|
||||
```toml
|
||||
[target.example]
|
||||
type = "executable"
|
||||
compile-features = [ "cxx_std_20", "c_std_11" ]
|
||||
```
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
# Automatically generated from tests/cxx-standard/cmake.toml - DO NOT EDIT
|
||||
layout: default
|
||||
title: Changing C++ standard
|
||||
permalink: /examples/cxx-standard
|
||||
parent: Examples
|
||||
nav_order: 5
|
||||
---
|
||||
|
||||
# Changing C++ standard
|
||||
|
||||
Require a C++11 compiler for the target `example`.
|
||||
|
||||
```toml
|
||||
[project]
|
||||
name = "cxx-standard"
|
||||
description = "Changing C++ standard"
|
||||
|
||||
[target.example]
|
||||
type = "executable"
|
||||
sources = ["src/main.cpp"]
|
||||
compile-features = ["cxx_std_11"]
|
||||
```
|
||||
|
||||
This is equivalent to CMake's [target_compile_features](https://cmake.org/cmake/help/latest/command/target_compile_features.html)`(example PRIVATE cxx_std_11)`. For more information on available C/C++ standards and features see [cmake-compile-features(7)](https://cmake.org/cmake/help/latest/manual/cmake-compile-features.7.html).
|
||||
|
||||
<sup><sub>This page was automatically generated from [tests/cxx-standard/cmake.toml](https://github.com/build-cpp/cmkr/tree/main/tests/cxx-standard/cmake.toml).</sub></sup>
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
# Automatically generated from tests/fetch-content/cmake.toml - DO NOT EDIT
|
||||
layout: default
|
||||
title: Fetching from git
|
||||
permalink: /examples/fetch-content
|
||||
parent: Examples
|
||||
nav_order: 2
|
||||
---
|
||||
|
||||
# Fetching from git
|
||||
|
||||
Downloads [fmt v7.1.3](https://fmt.dev/7.1.3/) from [GitHub](https://github.com) and links an `example` target to it:
|
||||
|
||||
```toml
|
||||
[project]
|
||||
name = "fetch-content"
|
||||
description = "Fetching from git"
|
||||
|
||||
[fetch-content]
|
||||
fmt = { git = "https://github.com/fmtlib/fmt", tag = "7.1.3" }
|
||||
|
||||
[target.example]
|
||||
type = "executable"
|
||||
sources = ["src/main.cpp"]
|
||||
link-libraries = ["fmt::fmt"]
|
||||
```
|
||||
|
||||
This is equivalent to calling CMake's [FetchContent](https://cmake.org/cmake/help/latest/module/FetchContent.html).
|
||||
|
||||
<sup><sub>This page was automatically generated from [tests/fetch-content/cmake.toml](https://github.com/build-cpp/cmkr/tree/main/tests/fetch-content/cmake.toml).</sub></sup>
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
# Automatically generated from tests/globbing/cmake.toml - DO NOT EDIT
|
||||
layout: default
|
||||
title: Globbing sources
|
||||
permalink: /examples/globbing
|
||||
parent: Examples
|
||||
nav_order: 6
|
||||
---
|
||||
|
||||
# Globbing sources
|
||||
|
||||
|
||||
|
||||
```toml
|
||||
[project]
|
||||
name = "globbing"
|
||||
description = "Globbing sources"
|
||||
|
||||
# Recursively glob in the mylib/ folder
|
||||
[target.mylib]
|
||||
type = "static"
|
||||
alias = "mylib::mylib"
|
||||
sources = ["mylib/**.hpp", "mylib/**.cpp"]
|
||||
include-directories = ["mylib/include"]
|
||||
|
||||
# Single-folder glob in example/src/
|
||||
[target.example]
|
||||
type = "executable"
|
||||
sources = ["example/src/*.cpp"]
|
||||
link-libraries = ["mylib::mylib"]
|
||||
```
|
||||
|
||||
As you can see in the example above you can use `**.ext` to glob recursively and `*.ext` to glob non-recursively. This **does not** generate `file(GLOB ...)` commands, but instead globs when cmkr is run. Files are sorted to give deterministic results regardless of the platform used.
|
||||
|
||||
<sup><sub>This page was automatically generated from [tests/globbing/cmake.toml](https://github.com/build-cpp/cmkr/tree/main/tests/globbing/cmake.toml).</sub></sup>
|
||||
@@ -1,22 +0,0 @@
|
||||
---
|
||||
layout: default
|
||||
title: Import content from Github
|
||||
permalink: /examples/import-from-git
|
||||
parent: Examples
|
||||
nav_order: 2
|
||||
---
|
||||
|
||||
# Import content from Github
|
||||
|
||||
Importing an existing project called Zydis to my project
|
||||
|
||||
tag is optional but you can target any branch with it
|
||||
|
||||
```toml
|
||||
[fetch-content]
|
||||
zydis = { git = "https://github.com/zyantific/zydis.git", tag = "v3.1.0" }
|
||||
|
||||
[target.example]
|
||||
type = "executable"
|
||||
link-libraries = ["zydis"]
|
||||
```
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
layout: default
|
||||
title: Examples
|
||||
permalink: /examples/
|
||||
nav_order: 4
|
||||
has_children: true
|
||||
---
|
||||
|
||||
# Examples
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
layout: default
|
||||
title: Examples
|
||||
permalink: /examples/
|
||||
nav_order: 100
|
||||
has_children: true
|
||||
---
|
||||
|
||||
# Examples
|
||||
|
||||
The examples in this section are automatically generated from the [tests](https://github.com/build-cpp/cmkr/blob/main/tests/cmake.toml).
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
# Automatically generated from tests/interface/cmake.toml - DO NOT EDIT
|
||||
layout: default
|
||||
title: Header-only library
|
||||
permalink: /examples/interface
|
||||
parent: Examples
|
||||
nav_order: 1
|
||||
---
|
||||
|
||||
# Header-only library
|
||||
|
||||
|
||||
|
||||
```toml
|
||||
[project]
|
||||
name = "interface"
|
||||
description = "Header-only library"
|
||||
|
||||
[target.mylib]
|
||||
type = "interface"
|
||||
include-directories = ["include"]
|
||||
compile-features = ["cxx_std_11"]
|
||||
|
||||
[target.example]
|
||||
type = "executable"
|
||||
sources = ["src/main.cpp"]
|
||||
link-libraries = ["mylib"]
|
||||
```
|
||||
|
||||
|
||||
|
||||
<sup><sub>This page was automatically generated from [tests/interface/cmake.toml](https://github.com/build-cpp/cmkr/tree/main/tests/interface/cmake.toml).</sub></sup>
|
||||
@@ -1,23 +0,0 @@
|
||||
---
|
||||
layout: default
|
||||
title: Quickstart
|
||||
permalink: /examples/quickstart
|
||||
parent: Examples
|
||||
nav_order: 1
|
||||
---
|
||||
|
||||
# Quickstart
|
||||
|
||||
Smallest possible start point you can have using cmkr
|
||||
|
||||
```toml
|
||||
[cmake]
|
||||
version = "3.15"
|
||||
|
||||
[project]
|
||||
name = "hello-world"
|
||||
|
||||
[target.hello-world]
|
||||
type = "executable"
|
||||
sources = [ "src/*.cpp" ]
|
||||
```
|
||||
@@ -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>
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
# Automatically generated from tests/vcpkg/cmake.toml - DO NOT EDIT
|
||||
layout: default
|
||||
title: Dependencies from vcpkg
|
||||
permalink: /examples/vcpkg
|
||||
parent: Examples
|
||||
nav_order: 4
|
||||
---
|
||||
|
||||
# Dependencies from vcpkg
|
||||
|
||||
Downloads [fmt v7.1.3](https://fmt.dev/7.1.3/) using [vcpkg](https://vcpkg.io/) and links an `example` target to it:
|
||||
|
||||
```toml
|
||||
[project]
|
||||
name = "vcpkg"
|
||||
description = "Dependencies from vcpkg"
|
||||
|
||||
# See https://github.com/microsoft/vcpkg/releases for vcpkg versions
|
||||
# See https://vcpkg.io/en/packages.html for available packages
|
||||
[vcpkg]
|
||||
version = "2021.05.12"
|
||||
packages = ["fmt"]
|
||||
|
||||
[find-package]
|
||||
fmt = {}
|
||||
|
||||
[target.example]
|
||||
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`.
|
||||
|
||||
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>
|
||||
@@ -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.
|
||||
@@ -0,0 +1 @@
|
||||
google-site-verification: google13f7c659b54c069f.html
|
||||
+57
-3
@@ -6,18 +6,72 @@ nav_order: 0
|
||||
|
||||
# Index
|
||||
|
||||
[cmkr](https://github.com/build-cpp/cmkr), pronounced "cmaker", is a modern build system based on [CMake](https://cmake.org/) and [TOML](https://toml.io). 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
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
bundle install
|
||||
bundle exec jekyll serve --force_polling --livereload --incremental
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "project_parser.hpp"
|
||||
|
||||
namespace cmkr {
|
||||
namespace gen {
|
||||
|
||||
void generate_project(const std::string &type);
|
||||
|
||||
void generate_cmake(const char *path, const parser::Project *parent_project = nullptr);
|
||||
|
||||
} // namespace gen
|
||||
} // namespace cmkr
|
||||
@@ -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);
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace cmkr {
|
||||
namespace gen {
|
||||
|
||||
int generate_project(const char *typ);
|
||||
|
||||
int generate_cmake(const char *path, bool root = true);
|
||||
|
||||
} // namespace gen
|
||||
} // namespace cmkr
|
||||
|
||||
int cmkr_gen_generate_project(const char *typ);
|
||||
|
||||
int cmkr_gen_generate_cmake(const char *path);
|
||||
+88
-33
@@ -1,45 +1,100 @@
|
||||
#pragma once
|
||||
|
||||
static const char *hello_world = &R"lit(
|
||||
static const char *cpp_executable = &R"lit(
|
||||
#include <iostream>
|
||||
#include <cstdlib>
|
||||
|
||||
int %s() {
|
||||
std::cout << "Hello World!\n";
|
||||
return 0;
|
||||
int main() {
|
||||
std::cout << "Hello from cmkr!\n";
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
)lit"[1]; // skip initial newline
|
||||
|
||||
static const char *cmake_toml = &R"lit(
|
||||
[cmake]
|
||||
version = "3.15"
|
||||
# subdirs = []
|
||||
# build-dir = ""
|
||||
# cpp-flags = []
|
||||
# c-flags = []
|
||||
# link-flags = []
|
||||
# generator = ""
|
||||
# arguments = []
|
||||
static const char *cpp_library = &R"lit(
|
||||
#include <@name/@name.hpp>
|
||||
|
||||
[project]
|
||||
name = "%s"
|
||||
version = "0.1.0"
|
||||
#include <iostream>
|
||||
|
||||
# [find-package]
|
||||
namespace @name {
|
||||
|
||||
# [fetch-content]
|
||||
void hello() {
|
||||
std::cout << "Hello from cmkr!\n";
|
||||
}
|
||||
|
||||
# [options]
|
||||
|
||||
[target.%s]
|
||||
type = "%s"
|
||||
sources = ["src/*.cpp"]
|
||||
include-directories = ["include"]
|
||||
# alias = ""
|
||||
# compile-features = []
|
||||
# compile-definitions = []
|
||||
# link-libraries = []
|
||||
|
||||
[[install]]
|
||||
%s = ["%s"]
|
||||
destination = "${CMAKE_INSTALL_PREFIX}/%s"
|
||||
} // namespace @name
|
||||
)lit"[1]; // skip initial newline
|
||||
|
||||
static const char *hpp_library = &R"lit(
|
||||
#pragma once
|
||||
|
||||
namespace @name {
|
||||
|
||||
void hello();
|
||||
|
||||
} // namespace @name
|
||||
)lit"[1]; // skip initial newline
|
||||
|
||||
static const char *hpp_interface = &R"lit(
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace @name {
|
||||
|
||||
inline void hello() {
|
||||
std::cout << "Hello from cmkr!\n";
|
||||
}
|
||||
|
||||
} // namespace @name
|
||||
)lit"[1]; // skip initial newline
|
||||
|
||||
static const char *toml_executable = &R"lit(
|
||||
# Reference: https://build-cpp.github.io/cmkr/cmake-toml
|
||||
[project]
|
||||
name = "@name"
|
||||
|
||||
[target.@name]
|
||||
type = "executable"
|
||||
sources = ["src/@name/main.cpp"]
|
||||
compile-features = ["cxx_std_11"]
|
||||
)lit"[1]; // skip initial newline
|
||||
|
||||
static const char *toml_library = &R"lit(
|
||||
# Reference: https://build-cpp.github.io/cmkr/cmake-toml
|
||||
[project]
|
||||
name = "@name"
|
||||
|
||||
[target.@name]
|
||||
type = "@type"
|
||||
sources = [
|
||||
"src/@name/@name.cpp",
|
||||
"include/@name/@name.hpp"
|
||||
]
|
||||
include-directories = ["include"]
|
||||
compile-features = ["cxx_std_11"]
|
||||
)lit"[1]; // skip initial newline
|
||||
|
||||
static const char *toml_interface = &R"lit(
|
||||
# Reference: https://build-cpp.github.io/cmkr/cmake-toml
|
||||
[project]
|
||||
name = "@name"
|
||||
|
||||
[target.@name]
|
||||
type = "interface"
|
||||
include-directories = ["include"]
|
||||
compile-features = ["cxx_std_11"]
|
||||
)lit"[1]; // skip initial newline
|
||||
|
||||
static const char *toml_migration = &R"lit(
|
||||
# Reference: https://build-cpp.github.io/cmkr/cmake-toml
|
||||
[project]
|
||||
name = "@name"
|
||||
|
||||
# TODO: define a target for each of your executables/libraries like this:
|
||||
#[target.myexecutable]
|
||||
#type = "executable" # static, shared
|
||||
#sources = ["src/@name/*.cpp", "include/@name/*.hpp"]
|
||||
#include-directories = ["include"]
|
||||
#compile-features = ["cxx_std_11"]
|
||||
#link-libraries = ["target-or-library"]
|
||||
)lit"[1]; // skip initial newline
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <mpark/variant.hpp>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <tsl/ordered_map.h>
|
||||
#include <vector>
|
||||
|
||||
namespace cmkr {
|
||||
namespace cmake {
|
||||
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;
|
||||
@@ -26,15 +29,23 @@ struct Option {
|
||||
|
||||
struct Package {
|
||||
std::string name;
|
||||
std::string condition;
|
||||
std::string version;
|
||||
bool required = true;
|
||||
bool config = true;
|
||||
bool config = false;
|
||||
std::vector<std::string> components;
|
||||
};
|
||||
|
||||
struct Vcpkg {
|
||||
std::string version;
|
||||
std::vector<std::string> packages;
|
||||
std::string url;
|
||||
|
||||
struct Package {
|
||||
std::string name;
|
||||
std::vector<std::string> features;
|
||||
};
|
||||
|
||||
std::vector<Package> packages;
|
||||
};
|
||||
|
||||
enum TargetType {
|
||||
@@ -44,30 +55,49 @@ enum TargetType {
|
||||
target_static,
|
||||
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;
|
||||
|
||||
// https://cmake.org/cmake/help/latest/manual/cmake-commands.7.html#project-commands
|
||||
ConditionVector compile_definitions;
|
||||
ConditionVector compile_features;
|
||||
ConditionVector compile_options;
|
||||
ConditionVector include_directories;
|
||||
ConditionVector link_directories;
|
||||
ConditionVector link_libraries;
|
||||
ConditionVector link_options;
|
||||
ConditionVector precompile_headers;
|
||||
ConditionVector sources;
|
||||
ConditionVector private_compile_definitions;
|
||||
|
||||
ConditionVector compile_features;
|
||||
ConditionVector private_compile_features;
|
||||
|
||||
ConditionVector compile_options;
|
||||
ConditionVector private_compile_options;
|
||||
|
||||
ConditionVector include_directories;
|
||||
ConditionVector private_include_directories;
|
||||
|
||||
ConditionVector link_directories;
|
||||
ConditionVector private_link_directories;
|
||||
|
||||
ConditionVector link_libraries;
|
||||
ConditionVector private_link_libraries;
|
||||
|
||||
ConditionVector link_options;
|
||||
ConditionVector private_link_options;
|
||||
|
||||
ConditionVector precompile_headers;
|
||||
ConditionVector private_precompile_headers;
|
||||
|
||||
std::string condition;
|
||||
std::string alias;
|
||||
tsl::ordered_map<std::string, std::string> properties;
|
||||
Condition<tsl::ordered_map<std::string, std::string>> properties;
|
||||
|
||||
Condition<std::string> cmake_before;
|
||||
Condition<std::string> cmake_after;
|
||||
@@ -75,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;
|
||||
@@ -84,6 +121,7 @@ struct Test {
|
||||
};
|
||||
|
||||
struct Install {
|
||||
std::string condition;
|
||||
std::vector<std::string> targets;
|
||||
std::vector<std::string> files;
|
||||
std::vector<std::string> dirs;
|
||||
@@ -91,14 +129,30 @@ struct Install {
|
||||
std::string destination;
|
||||
};
|
||||
|
||||
struct CMake {
|
||||
struct Subdir {
|
||||
std::string name;
|
||||
std::string condition;
|
||||
Condition<std::string> cmake_before;
|
||||
Condition<std::string> cmake_after;
|
||||
ConditionVector include_before;
|
||||
ConditionVector include_after;
|
||||
};
|
||||
|
||||
struct Content {
|
||||
std::string name;
|
||||
std::string condition;
|
||||
tsl::ordered_map<std::string, std::string> arguments;
|
||||
};
|
||||
|
||||
struct Project {
|
||||
// This is the CMake version required to use all cmkr versions.
|
||||
std::string cmake_version = "3.15";
|
||||
std::string cmkr_include = "cmkr.cmake";
|
||||
std::string build_dir = "build";
|
||||
std::string generator;
|
||||
std::string config;
|
||||
Condition<std::vector<std::string>> subdirs;
|
||||
bool allow_in_tree = false;
|
||||
Condition<std::vector<std::string>> project_subdirs;
|
||||
std::vector<std::string> cppflags;
|
||||
std::vector<std::string> cflags;
|
||||
std::vector<std::string> linkflags;
|
||||
@@ -116,14 +170,18 @@ struct CMake {
|
||||
std::vector<Option> options;
|
||||
std::vector<Package> packages;
|
||||
Vcpkg vcpkg;
|
||||
tsl::ordered_map<std::string, std::map<std::string, std::string>> contents;
|
||||
std::vector<Content> contents;
|
||||
std::vector<Template> templates;
|
||||
std::vector<Target> targets;
|
||||
std::vector<Test> tests;
|
||||
std::vector<Install> installs;
|
||||
tsl::ordered_map<std::string, std::string> conditions;
|
||||
std::vector<Subdir> subdirs;
|
||||
|
||||
CMake(const std::string &path, bool build);
|
||||
Project(const Project *parent, const std::string &path, bool build);
|
||||
};
|
||||
|
||||
} // namespace cmake
|
||||
bool is_root_path(const std::string &path);
|
||||
|
||||
} // namespace parser
|
||||
} // namespace cmkr
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "args.hpp"
|
||||
#include "arguments.hpp"
|
||||
#include "build.hpp"
|
||||
#include "gen.hpp"
|
||||
#include "cmake_generator.hpp"
|
||||
#include "help.hpp"
|
||||
|
||||
#include "fs.hpp"
|
||||
@@ -21,9 +21,7 @@ const char *handle_args(int argc, char **argv) {
|
||||
throw std::runtime_error(cmkr::help::message());
|
||||
std::string main_arg = args[1];
|
||||
if (main_arg == "gen") {
|
||||
auto ret = cmkr::gen::generate_cmake(fs::current_path().string().c_str());
|
||||
if (ret)
|
||||
return "CMake generation error!";
|
||||
cmkr::gen::generate_cmake(fs::current_path().string().c_str());
|
||||
return "CMake generation successful!";
|
||||
} else if (main_arg == "help") {
|
||||
return cmkr::help::message();
|
||||
@@ -33,15 +31,14 @@ const char *handle_args(int argc, char **argv) {
|
||||
std::string type = "executable";
|
||||
if (args.size() > 2)
|
||||
type = args[2];
|
||||
auto ret = cmkr::gen::generate_project(type.c_str());
|
||||
if (ret)
|
||||
return "Initialization failure!";
|
||||
cmkr::gen::generate_project(type.c_str());
|
||||
cmkr::gen::generate_cmake(fs::current_path().string().c_str());
|
||||
return "Directory initialized!";
|
||||
} else if (main_arg == "build") {
|
||||
auto ret = build::run(argc, argv);
|
||||
if (ret)
|
||||
return "CMake build error!";
|
||||
return "CMake run completed!";
|
||||
return "CMake build completed!";
|
||||
} else if (main_arg == "install") {
|
||||
auto ret = build::install();
|
||||
if (ret)
|
||||
+23
-24
@@ -1,46 +1,45 @@
|
||||
#include "build.hpp"
|
||||
#include "cmake.hpp"
|
||||
#include "cmake_generator.hpp"
|
||||
#include "error.hpp"
|
||||
#include "gen.hpp"
|
||||
#include "project_parser.hpp"
|
||||
|
||||
#include "fs.hpp"
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <sstream>
|
||||
#include <stddef.h>
|
||||
#include <stdexcept>
|
||||
#include <stdlib.h>
|
||||
#include <system_error>
|
||||
|
||||
namespace cmkr {
|
||||
namespace build {
|
||||
|
||||
int run(int argc, char **argv) {
|
||||
cmake::CMake cmake(".", true);
|
||||
parser::Project project(nullptr, ".", true);
|
||||
if (argc > 2) {
|
||||
for (int i = 2; i < argc; ++i) {
|
||||
cmake.build_args.push_back(argv[i]);
|
||||
project.build_args.push_back(argv[i]);
|
||||
}
|
||||
}
|
||||
std::stringstream ss;
|
||||
|
||||
if (gen::generate_cmake(fs::current_path().string().c_str()))
|
||||
throw std::runtime_error("CMake generation failure!");
|
||||
gen::generate_cmake(fs::current_path().string().c_str());
|
||||
|
||||
ss << "cmake -S. -B" << cmake.build_dir << " ";
|
||||
ss << "cmake -DCMKR_BUILD_SKIP_GENERATION=ON -B" << project.build_dir << " ";
|
||||
|
||||
if (!cmake.generator.empty()) {
|
||||
ss << "-G \"" << cmake.generator << "\" ";
|
||||
if (!project.generator.empty()) {
|
||||
ss << "-G \"" << project.generator << "\" ";
|
||||
}
|
||||
if (!cmake.config.empty()) {
|
||||
ss << "-DCMAKE_BUILD_TYPE=" << cmake.config << " ";
|
||||
if (!project.config.empty()) {
|
||||
ss << "-DCMAKE_BUILD_TYPE=" << project.config << " ";
|
||||
}
|
||||
if (!cmake.gen_args.empty()) {
|
||||
for (const auto &arg : cmake.gen_args) {
|
||||
if (!project.gen_args.empty()) {
|
||||
for (const auto &arg : project.gen_args) {
|
||||
ss << "-D" << arg << " ";
|
||||
}
|
||||
}
|
||||
ss << "&& cmake --build " << cmake.build_dir << " --parallel";
|
||||
ss << "&& cmake --build " << project.build_dir << " --parallel";
|
||||
if (argc > 2) {
|
||||
for (const auto &arg : cmake.build_args) {
|
||||
for (const auto &arg : project.build_args) {
|
||||
ss << " " << arg;
|
||||
}
|
||||
}
|
||||
@@ -50,17 +49,17 @@ int run(int argc, char **argv) {
|
||||
|
||||
int clean() {
|
||||
bool ret = false;
|
||||
cmake::CMake cmake(".", true);
|
||||
if (fs::exists(cmake.build_dir)) {
|
||||
ret = fs::remove_all(cmake.build_dir);
|
||||
fs::create_directory(cmake.build_dir);
|
||||
parser::Project project(nullptr, ".", true);
|
||||
if (fs::exists(project.build_dir)) {
|
||||
ret = fs::remove_all(project.build_dir);
|
||||
fs::create_directory(project.build_dir);
|
||||
}
|
||||
return !ret;
|
||||
}
|
||||
|
||||
int install() {
|
||||
cmake::CMake cmake(".", false);
|
||||
auto cmd = "cmake --install " + cmake.build_dir;
|
||||
parser::Project project(nullptr, ".", false);
|
||||
auto cmd = "cmake --install " + project.build_dir;
|
||||
return ::system(cmd.c_str());
|
||||
}
|
||||
} // namespace build
|
||||
@@ -94,4 +93,4 @@ int cmkr_build_install(void) {
|
||||
} catch (...) {
|
||||
return cmkr::error::Status(cmkr::error::Status::Code::InstallError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-277
@@ -1,277 +0,0 @@
|
||||
#include "cmake.hpp"
|
||||
|
||||
#include "enum_helper.hpp"
|
||||
#include "fs.hpp"
|
||||
#include <stdexcept>
|
||||
#include <toml.hpp>
|
||||
#include <tsl/ordered_map.h>
|
||||
|
||||
template <>
|
||||
const char *enumStrings<cmkr::cmake::TargetType>::data[] = {"executable", "library", "shared", "static", "interface", "custom"};
|
||||
|
||||
namespace cmkr {
|
||||
namespace cmake {
|
||||
|
||||
using TomlBasicValue = toml::basic_value<toml::preserve_comments, tsl::ordered_map, std::vector>;
|
||||
|
||||
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;
|
||||
}
|
||||
throw std::runtime_error("Unknown " + help_name + "'" + str + "'! Supported types are: " + supported);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void get_optional(const TomlBasicValue &v, const toml::key &ky, T &destination);
|
||||
|
||||
template <typename T>
|
||||
static void get_optional(const TomlBasicValue &v, const toml::key &ky, Condition<T> &destination) {
|
||||
// TODO: this algorithm in O(n) over the amount of keys, kinda bad
|
||||
const auto &table = v.as_table();
|
||||
for (const auto &itr : table) {
|
||||
const auto &key = itr.first;
|
||||
const auto &value = itr.second;
|
||||
if (value.is_table()) {
|
||||
if (value.contains(ky)) {
|
||||
destination[key] = toml::find<T>(value, ky);
|
||||
}
|
||||
} else if (key == ky) {
|
||||
destination[""] = toml::find<T>(v, ky);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void get_optional(const TomlBasicValue &v, const toml::key &ky, T &destination) {
|
||||
if (v.contains(ky)) {
|
||||
destination = toml::find<T>(v, ky);
|
||||
}
|
||||
}
|
||||
|
||||
CMake::CMake(const std::string &path, bool build) {
|
||||
if (!fs::exists(fs::path(path) / "cmake.toml")) {
|
||||
throw std::runtime_error("No cmake.toml was found!");
|
||||
}
|
||||
const auto toml = toml::parse<toml::preserve_comments, tsl::ordered_map, std::vector>((fs::path(path) / "cmake.toml").string());
|
||||
if (build) {
|
||||
if (toml.contains("cmake")) {
|
||||
const auto &cmake = toml::find(toml, "cmake");
|
||||
|
||||
if (cmake.contains("bin-dir")) {
|
||||
throw std::runtime_error("bin-dir has been renamed to build-dir");
|
||||
}
|
||||
|
||||
get_optional(cmake, "build-dir", build_dir);
|
||||
get_optional(cmake, "generator", generator);
|
||||
get_optional(cmake, "config", config);
|
||||
get_optional(cmake, "arguments", gen_args);
|
||||
}
|
||||
} else {
|
||||
if (toml.contains("cmake")) {
|
||||
const auto &cmake = toml::find(toml, "cmake");
|
||||
cmake_version = toml::find(cmake, "version").as_string();
|
||||
get_optional(cmake, "cmkr-include", cmkr_include);
|
||||
get_optional(cmake, "cpp-flags", cppflags);
|
||||
get_optional(cmake, "c-flags", cflags);
|
||||
get_optional(cmake, "link-flags", linkflags);
|
||||
}
|
||||
|
||||
if (toml.contains("project")) {
|
||||
const auto &project = toml::find(toml, "project");
|
||||
project_name = toml::find(project, "name").as_string();
|
||||
get_optional(project, "version", project_version);
|
||||
get_optional(project, "description", project_description);
|
||||
get_optional(project, "languages", project_languages);
|
||||
get_optional(project, "cmake-before", cmake_before);
|
||||
get_optional(project, "cmake-after", cmake_after);
|
||||
get_optional(project, "include-before", include_before);
|
||||
get_optional(project, "include-after", include_after);
|
||||
get_optional(project, "subdirs", subdirs);
|
||||
}
|
||||
|
||||
if (toml.contains("settings")) {
|
||||
using set_map = std::map<std::string, TomlBasicValue>;
|
||||
const auto &sets = toml::find<set_map>(toml, "settings");
|
||||
for (const auto &set : sets) {
|
||||
Setting s;
|
||||
s.name = set.first;
|
||||
if (set.second.is_boolean()) {
|
||||
s.val = set.second.as_boolean();
|
||||
} else if (set.second.is_string()) {
|
||||
s.val = set.second.as_string();
|
||||
} else {
|
||||
get_optional(set.second, "comment", s.comment);
|
||||
if (set.second.contains("value")) {
|
||||
auto v = toml::find(set.second, "value");
|
||||
if (v.is_boolean()) {
|
||||
s.val = v.as_boolean();
|
||||
} else {
|
||||
s.val = v.as_string();
|
||||
}
|
||||
}
|
||||
get_optional(set.second, "cache", s.cache);
|
||||
get_optional(set.second, "force", s.force);
|
||||
}
|
||||
settings.push_back(s);
|
||||
}
|
||||
}
|
||||
|
||||
if (toml.contains("options")) {
|
||||
using opts_map = tsl::ordered_map<std::string, TomlBasicValue>;
|
||||
const auto &opts = toml::find<opts_map>(toml, "options");
|
||||
for (const auto &opt : opts) {
|
||||
Option o;
|
||||
o.name = opt.first;
|
||||
if (opt.second.is_boolean()) {
|
||||
o.val = opt.second.as_boolean();
|
||||
} else {
|
||||
get_optional(opt.second, "comment", o.comment);
|
||||
get_optional(opt.second, "value", o.val);
|
||||
}
|
||||
options.push_back(o);
|
||||
}
|
||||
}
|
||||
|
||||
if (toml.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 &pkg : pkgs) {
|
||||
Package p;
|
||||
p.name = pkg.first;
|
||||
if (pkg.second.is_string()) {
|
||||
p.version = pkg.second.as_string();
|
||||
} else {
|
||||
get_optional(pkg.second, "version", p.version);
|
||||
get_optional(pkg.second, "required", p.required);
|
||||
get_optional(pkg.second, "config", p.config);
|
||||
get_optional(pkg.second, "components", p.components);
|
||||
}
|
||||
packages.push_back(p);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: refactor to std::vector<Content> instead of this hacky thing?
|
||||
get_optional(toml, "fetch-content", contents);
|
||||
|
||||
if (toml.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();
|
||||
|
||||
for (const auto &itr : ts) {
|
||||
const auto &t = itr.second;
|
||||
Target target;
|
||||
target.name = itr.first;
|
||||
target.type = to_enum<TargetType>(toml::find(t, "type").as_string(), "target type");
|
||||
|
||||
get_optional(t, "sources", target.sources);
|
||||
get_optional(t, "compile-definitions", target.compile_definitions);
|
||||
get_optional(t, "compile-features", target.compile_features);
|
||||
get_optional(t, "compile-options", target.compile_options);
|
||||
get_optional(t, "include-directories", target.include_directories);
|
||||
get_optional(t, "link-directories", target.link_directories);
|
||||
get_optional(t, "link-libraries", target.link_libraries);
|
||||
get_optional(t, "link-options", target.link_options);
|
||||
get_optional(t, "precompile-headers", target.precompile_headers);
|
||||
|
||||
if (t.contains("alias")) {
|
||||
target.alias = toml::find(t, "alias").as_string();
|
||||
}
|
||||
|
||||
if (t.contains("properties")) {
|
||||
const auto &props = toml::find(t, "properties").as_table();
|
||||
for (const auto &propKv : props) {
|
||||
if (propKv.second.is_array()) {
|
||||
std::string property_list;
|
||||
for (const auto &list_val : propKv.second.as_array()) {
|
||||
if (!property_list.empty()) {
|
||||
property_list += ';';
|
||||
}
|
||||
property_list += list_val.as_string();
|
||||
}
|
||||
target.properties[propKv.first] = property_list;
|
||||
} else {
|
||||
target.properties[propKv.first] = propKv.second.as_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get_optional(t, "cmake-before", target.cmake_before);
|
||||
get_optional(t, "cmake-after", target.cmake_after);
|
||||
get_optional(t, "include-before", target.include_before);
|
||||
get_optional(t, "include-after", target.include_after);
|
||||
|
||||
targets.push_back(target);
|
||||
}
|
||||
}
|
||||
|
||||
if (toml.contains("test")) {
|
||||
const auto &ts = toml::find(toml, "test").as_array();
|
||||
for (const auto &t : ts) {
|
||||
Test test;
|
||||
test.name = toml::find(t, "name").as_string();
|
||||
get_optional(t, "configurations", test.configurations);
|
||||
get_optional(t, "working-directory", test.working_directory);
|
||||
test.command = toml::find(t, "command").as_string();
|
||||
get_optional(t, "arguments", test.arguments);
|
||||
tests.push_back(test);
|
||||
}
|
||||
}
|
||||
|
||||
if (toml.contains("install")) {
|
||||
const auto &ts = toml::find(toml, "install").as_array();
|
||||
for (const auto &t : ts) {
|
||||
Install inst;
|
||||
get_optional(t, "targets", inst.targets);
|
||||
get_optional(t, "files", inst.files);
|
||||
get_optional(t, "dirs", inst.dirs);
|
||||
get_optional(t, "configs", inst.configs);
|
||||
inst.destination = toml::find(t, "destination").as_string();
|
||||
installs.push_back(inst);
|
||||
}
|
||||
}
|
||||
|
||||
if (toml.contains("vcpkg")) {
|
||||
const auto &v = toml::find(toml, "vcpkg");
|
||||
vcpkg.version = toml::find(v, "version").as_string();
|
||||
vcpkg.packages = toml::find<decltype(vcpkg.packages)>(v, "packages");
|
||||
|
||||
// This allows the user to use a custom pmm version if desired
|
||||
if (contents.count("pmm") == 0) {
|
||||
contents["pmm"]["url"] = "https://github.com/vector-of-bool/pmm/archive/refs/tags/1.5.1.tar.gz";
|
||||
// Hack to not execute pmm's example CMakeLists.txt
|
||||
contents["pmm"]["SOURCE_SUBDIR"] = "pmm";
|
||||
}
|
||||
}
|
||||
|
||||
// Reasonable default conditions (you can override these if you desire)
|
||||
conditions["windows"] = R"cmake(WIN32)cmake";
|
||||
conditions["macos"] = R"cmake("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin")cmake";
|
||||
conditions["unix"] = R"cmake(UNIX)cmake";
|
||||
conditions["bsd"] = R"cmake("${CMAKE_SYSTEM_NAME}" MATCHES "BSD")cmake";
|
||||
conditions["linux"] = conditions["lunix"] = R"cmake("${CMAKE_SYSTEM_NAME}" MATCHES "Linux")cmake";
|
||||
|
||||
if (toml.contains("conditions")) {
|
||||
auto conds = toml::find<decltype(conditions)>(toml, "conditions");
|
||||
for (const auto &cond : conds) {
|
||||
conditions[cond.first] = cond.second;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace cmake
|
||||
} // namespace cmkr
|
||||
File diff suppressed because it is too large
Load Diff
+6
-5
@@ -1,6 +1,6 @@
|
||||
#include "error.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
#include <cassert>
|
||||
|
||||
namespace cmkr {
|
||||
namespace error {
|
||||
@@ -14,11 +14,12 @@ Status::Code Status::code() const noexcept { return ec_; }
|
||||
} // namespace error
|
||||
} // namespace cmkr
|
||||
|
||||
const char *err_string[] = {
|
||||
"Success", "Runtime error", "Initialization error", "CMake generation error", "Build error",
|
||||
// strings for cmkr::error::Status::Code
|
||||
static const char *err_string[] = {
|
||||
"Success", "Runtime error", "Initialization error", "CMake generation error", "Build error", "Clean error", "Install error",
|
||||
};
|
||||
|
||||
const char *cmkr_error_status(int i) {
|
||||
assert(i >= 0 && i < 5);
|
||||
assert(i >= 0 && i < (sizeof(err_string) / sizeof(*(err_string))));
|
||||
return err_string[i];
|
||||
}
|
||||
}
|
||||
|
||||
-806
@@ -1,806 +0,0 @@
|
||||
#include "gen.hpp"
|
||||
#include "cmake.hpp"
|
||||
#include "error.hpp"
|
||||
#include "literals.hpp"
|
||||
|
||||
#include "fs.hpp"
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <new>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace cmkr {
|
||||
namespace gen {
|
||||
|
||||
inline 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;
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
std::string format(const char *fmt, Args... args) {
|
||||
auto sz = snprintf(nullptr, 0, fmt, args...) + 1;
|
||||
char *buf = new char[sz];
|
||||
int ret = snprintf(buf, sz, fmt, args...);
|
||||
if (ret != sz - 1)
|
||||
throw std::runtime_error("Error formatting string!");
|
||||
std::string temp(buf, buf + sz - 1);
|
||||
delete[] buf;
|
||||
return temp;
|
||||
}
|
||||
|
||||
static std::vector<std::string> expand_cmake_path(const fs::path &name, const fs::path &toml_dir) {
|
||||
std::vector<std::string> temp;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
auto stem = name.filename().stem().string();
|
||||
auto ext = name.extension();
|
||||
|
||||
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) {
|
||||
temp.push_back(extract_suffix(toml_dir, f));
|
||||
}
|
||||
}
|
||||
} else if (stem == "**") {
|
||||
for (const auto &f : fs::recursive_directory_iterator(toml_dir / name.parent_path(), fs::directory_options::follow_directory_symlink)) {
|
||||
if (!f.is_directory() && f.path().extension() == ext) {
|
||||
temp.push_back(extract_suffix(toml_dir, f.path()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
temp.push_back(name.string());
|
||||
}
|
||||
// Normalize all paths to work with CMake (it needs a / on Windows as well)
|
||||
for (auto &path : temp) {
|
||||
std::replace(path.begin(), path.end(), '\\', '/');
|
||||
}
|
||||
// Sort paths alphabetically for consistent cross-OS generation
|
||||
std::sort(temp.begin(), temp.end());
|
||||
return temp;
|
||||
}
|
||||
|
||||
static std::vector<std::string> expand_cmake_paths(const std::vector<std::string> &sources, const fs::path &toml_dir) {
|
||||
// TODO: add duplicate checking
|
||||
std::vector<std::string> result;
|
||||
for (const auto &src : sources) {
|
||||
auto expanded = expand_cmake_path(src, toml_dir);
|
||||
for (const auto &f : expanded) {
|
||||
result.push_back(f);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int generate_project(const char *str) {
|
||||
fs::create_directory("src");
|
||||
fs::create_directory("include");
|
||||
const auto dir_name = fs::current_path().stem().string();
|
||||
std::string mainbuf;
|
||||
std::string installed;
|
||||
std::string target;
|
||||
std::string dest;
|
||||
if (!strcmp(str, "executable")) {
|
||||
mainbuf = format(hello_world, "main");
|
||||
installed = "targets";
|
||||
target = dir_name;
|
||||
dest = "bin";
|
||||
} else if (!strcmp(str, "static") || !strcmp(str, "shared") || !strcmp(str, "library")) {
|
||||
mainbuf = format(hello_world, "test");
|
||||
installed = "targets";
|
||||
target = dir_name;
|
||||
dest = "lib";
|
||||
} else if (!strcmp(str, "interface")) {
|
||||
installed = "files";
|
||||
target = "include/*.h";
|
||||
dest = "include/" + dir_name;
|
||||
} else {
|
||||
throw std::runtime_error("Unknown project type " + std::string(str) +
|
||||
"! Supported types are: executable, library, shared, static, interface");
|
||||
}
|
||||
|
||||
const auto tomlbuf = format(cmake_toml, dir_name.c_str(), dir_name.c_str(), str, installed.c_str(), target.c_str(), dest.c_str());
|
||||
|
||||
if (strcmp(str, "interface")) {
|
||||
std::ofstream ofs("src/main.cpp");
|
||||
if (ofs.is_open()) {
|
||||
ofs << mainbuf;
|
||||
}
|
||||
ofs.flush();
|
||||
ofs.close();
|
||||
}
|
||||
|
||||
std::ofstream ofs2("cmake.toml");
|
||||
if (ofs2.is_open()) {
|
||||
ofs2 << tomlbuf;
|
||||
}
|
||||
ofs2.flush();
|
||||
ofs2.close();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct CommandEndl {
|
||||
std::stringstream &ss;
|
||||
CommandEndl(std::stringstream &ss) : ss(ss) {}
|
||||
void endl() { ss << '\n'; }
|
||||
};
|
||||
|
||||
struct RawArg {
|
||||
RawArg() = default;
|
||||
RawArg(std::string arg) : arg(std::move(arg)) {}
|
||||
|
||||
std::string arg;
|
||||
};
|
||||
|
||||
// Credit: JustMagic
|
||||
struct Command {
|
||||
std::stringstream &ss;
|
||||
int depth = 0;
|
||||
std::string command;
|
||||
bool first_arg = true;
|
||||
bool had_newline = false;
|
||||
bool generated = false;
|
||||
|
||||
Command(std::stringstream &ss, int depth, const std::string &command) : ss(ss), depth(depth), command(command) {}
|
||||
|
||||
~Command() {
|
||||
if (!generated) {
|
||||
assert(false && "Incorrect usage of cmd()");
|
||||
}
|
||||
}
|
||||
|
||||
std::string quote(const std::string &str) {
|
||||
// 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) {
|
||||
return str;
|
||||
}
|
||||
std::string result;
|
||||
result += "\"";
|
||||
for (char ch : str) {
|
||||
switch (ch) {
|
||||
case '\\':
|
||||
case '\"':
|
||||
result += '\\';
|
||||
default:
|
||||
result += ch;
|
||||
break;
|
||||
}
|
||||
}
|
||||
result += "\"";
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::string indent(int n) {
|
||||
std::string result;
|
||||
for (int i = 0; i < n; i++) {
|
||||
result += '\t';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool print_arg(const std::vector<T> &vec) {
|
||||
if (vec.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
had_newline = true;
|
||||
for (const auto &value : vec) {
|
||||
print_arg(value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class Key, class Value>
|
||||
bool print_arg(const tsl::ordered_map<Key, Value> &map) {
|
||||
if (map.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const auto &itr : map) {
|
||||
print_arg(itr);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class K>
|
||||
bool print_arg(const std::pair<K, std::vector<std::string>> &kv) {
|
||||
if (kv.second.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
had_newline = true;
|
||||
print_arg(kv.first);
|
||||
depth++;
|
||||
for (const auto &s : kv.second) {
|
||||
print_arg(s);
|
||||
}
|
||||
depth--;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class K, class V>
|
||||
bool print_arg(const std::pair<K, V> &kv) {
|
||||
if (kv.second.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
had_newline = true;
|
||||
print_arg(kv.first);
|
||||
depth++;
|
||||
print_arg(kv.second);
|
||||
depth--;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool print_arg(const RawArg &arg) {
|
||||
if (arg.arg.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (had_newline) {
|
||||
first_arg = false;
|
||||
ss << '\n' << indent(depth + 1);
|
||||
} else if (first_arg) {
|
||||
first_arg = false;
|
||||
} else {
|
||||
ss << ' ';
|
||||
}
|
||||
|
||||
ss << arg.arg;
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool print_arg(const T &value) {
|
||||
std::stringstream tmp;
|
||||
tmp << value;
|
||||
auto str = tmp.str();
|
||||
if (str.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (had_newline) {
|
||||
first_arg = false;
|
||||
ss << '\n' << indent(depth + 1);
|
||||
} else if (first_arg) {
|
||||
first_arg = false;
|
||||
} else {
|
||||
ss << ' ';
|
||||
}
|
||||
|
||||
ss << quote(str);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class... Ts>
|
||||
CommandEndl operator()(Ts &&...values) {
|
||||
generated = true;
|
||||
ss << indent(depth) << command << '(';
|
||||
(void)std::initializer_list<bool>{print_arg(values)...};
|
||||
if (had_newline)
|
||||
ss << '\n' << indent(depth);
|
||||
ss << ")\n";
|
||||
return CommandEndl(ss);
|
||||
}
|
||||
};
|
||||
|
||||
static std::string tolf(const std::string &str) {
|
||||
std::string result;
|
||||
for (char ch : str) {
|
||||
if (ch != '\r') {
|
||||
result += ch;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
struct Generator {
|
||||
Generator(cmake::CMake &cmake) : cmake(cmake) {}
|
||||
Generator(const Generator &) = delete;
|
||||
|
||||
cmake::CMake &cmake;
|
||||
std::stringstream ss;
|
||||
int indent = 0;
|
||||
|
||||
Command cmd(const std::string &command) {
|
||||
if (command.empty())
|
||||
throw std::invalid_argument("command cannot be empty");
|
||||
if (command == "if") {
|
||||
indent++;
|
||||
return Command(ss, indent - 1, command);
|
||||
} else if (command == "else" || command == "elseif") {
|
||||
return Command(ss, indent - 1, command);
|
||||
} else if (command == "endif") {
|
||||
indent--;
|
||||
}
|
||||
return Command(ss, indent, command);
|
||||
}
|
||||
|
||||
CommandEndl comment(const std::string &comment) {
|
||||
ss << Command::indent(indent) << "# " << comment << '\n';
|
||||
return CommandEndl(ss);
|
||||
}
|
||||
|
||||
void endl() { ss << '\n'; }
|
||||
|
||||
void inject_includes(const std::vector<std::string> &includes) {
|
||||
if (!includes.empty()) {
|
||||
for (const auto &file : includes) {
|
||||
if (!fs::is_regular_file(file)) {
|
||||
throw std::runtime_error("Include '" + file + "' does not exist");
|
||||
}
|
||||
cmd("include")(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void inject_cmake(const std::string &cmake) {
|
||||
if (!cmake.empty()) {
|
||||
if (cmake.back() == '\"') {
|
||||
throw std::runtime_error("Detected additional \" at the end of cmake block");
|
||||
}
|
||||
auto cmake_lf = tolf(cmake);
|
||||
while (cmake_lf.back() == '\n')
|
||||
cmake_lf.pop_back();
|
||||
bool did_indent = false;
|
||||
for (char ch : cmake_lf) {
|
||||
if (!did_indent) {
|
||||
ss << Command::indent(indent);
|
||||
did_indent = true;
|
||||
} else if (ch == '\n') {
|
||||
did_indent = false;
|
||||
}
|
||||
ss << ch;
|
||||
}
|
||||
ss << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename Lambda>
|
||||
void handle_condition(const cmake::Condition<T> &value, const Lambda &fn) {
|
||||
if (!value.empty()) {
|
||||
for (const auto &itr : value) {
|
||||
const auto &condition = itr.first;
|
||||
if (!condition.empty()) {
|
||||
if (cmake.conditions.count(condition) == 0) {
|
||||
// TODO: somehow print line number information here?
|
||||
throw std::runtime_error("Unknown condition '" + condition + "'");
|
||||
}
|
||||
cmd("if")(RawArg(cmake.conditions[condition]));
|
||||
}
|
||||
|
||||
if (!itr.second.empty()) {
|
||||
fn(condition, itr.second);
|
||||
}
|
||||
|
||||
if (!condition.empty()) {
|
||||
cmd("endif")();
|
||||
}
|
||||
endl();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
int generate_cmake(const char *path, bool root) {
|
||||
if (!fs::exists(fs::path(path) / "cmake.toml")) {
|
||||
throw std::runtime_error("No cmake.toml found!");
|
||||
}
|
||||
|
||||
cmake::CMake cmake(path, false);
|
||||
Generator gen(cmake);
|
||||
|
||||
// Helper lambdas for more convenient CMake generation
|
||||
auto &ss = gen.ss;
|
||||
auto cmd = [&gen](const std::string &comment) { return gen.cmd(comment); };
|
||||
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); };
|
||||
|
||||
comment("This file is automatically generated from cmake.toml - DO NOT EDIT");
|
||||
comment("See https://github.com/build-cpp/cmkr for more information");
|
||||
endl();
|
||||
|
||||
if (root) {
|
||||
cmd("cmake_minimum_required")("VERSION", cmake.cmake_version).endl();
|
||||
|
||||
comment("Regenerate CMakeLists.txt automatically in the root project");
|
||||
cmd("set")("CMKR_ROOT_PROJECT", "OFF");
|
||||
// clang-format off
|
||||
cmd("if")("CMAKE_CURRENT_SOURCE_DIR", "STREQUAL", "CMAKE_SOURCE_DIR");
|
||||
cmd("set")("CMKR_ROOT_PROJECT", "ON").endl();
|
||||
|
||||
comment("Bootstrap cmkr");
|
||||
cmd("include")(cmake.cmkr_include, "OPTIONAL", "RESULT_VARIABLE", "CMKR_INCLUDE_RESULT");
|
||||
cmd("if")("CMKR_INCLUDE_RESULT");
|
||||
cmd("cmkr")();
|
||||
cmd("endif")().endl();
|
||||
|
||||
comment("Enable folder support");
|
||||
cmd("set_property")("GLOBAL", "PROPERTY", "USE_FOLDERS", "ON");
|
||||
cmd("endif")().endl();
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
comment("Create a configure-time dependency on cmake.toml to improve IDE support");
|
||||
cmd("if")("CMKR_ROOT_PROJECT");
|
||||
cmd("configure_file")("cmake.toml", "cmake.toml", "COPYONLY");
|
||||
cmd("endif")().endl();
|
||||
// clang-format on
|
||||
|
||||
// TODO: remove support and replace with global compile-features
|
||||
if (!cmake.cppflags.empty()) {
|
||||
ss << "set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} \"";
|
||||
for (const auto &flag : cmake.cppflags) {
|
||||
ss << flag << " ";
|
||||
}
|
||||
ss << "\")\n\n";
|
||||
}
|
||||
|
||||
// TODO: remove support and replace with global compile-features
|
||||
if (!cmake.cflags.empty()) {
|
||||
ss << "set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS} \"";
|
||||
for (const auto &flag : cmake.cflags) {
|
||||
ss << flag << " ";
|
||||
}
|
||||
ss << "\")\n\n";
|
||||
}
|
||||
|
||||
// TODO: remove support and replace with global linker-flags
|
||||
if (!cmake.linkflags.empty()) {
|
||||
ss << "set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} \"";
|
||||
for (const auto &flag : cmake.linkflags) {
|
||||
ss << flag << " ";
|
||||
}
|
||||
ss << "\")\n\n";
|
||||
}
|
||||
|
||||
gen.handle_condition(cmake.include_before, [&](const std::string &, const std::vector<std::string> &includes) { inject_includes(includes); });
|
||||
gen.handle_condition(cmake.cmake_before, [&](const std::string &, const std::string &cmake) { inject_cmake(cmake); });
|
||||
|
||||
if (!cmake.project_name.empty()) {
|
||||
auto languages = std::make_pair("LANGUAGES", cmake.project_languages);
|
||||
auto version = std::make_pair("VERSION", cmake.project_version);
|
||||
auto description = std::make_pair("DESCRIPTION", cmake.project_description);
|
||||
cmd("project")(cmake.project_name, languages, version, description).endl();
|
||||
}
|
||||
|
||||
gen.handle_condition(cmake.include_after, [&](const std::string &, const std::vector<std::string> &includes) { inject_includes(includes); });
|
||||
gen.handle_condition(cmake.cmake_after, [&](const std::string &, const std::string &cmake) { inject_cmake(cmake); });
|
||||
|
||||
if (!cmake.contents.empty()) {
|
||||
cmd("include")("FetchContent").endl();
|
||||
for (const auto &dep : cmake.contents) {
|
||||
cmd("message")("STATUS", "Fetching " + dep.first + "...");
|
||||
ss << "FetchContent_Declare(\n\t" << dep.first << "\n";
|
||||
for (const auto &arg : dep.second) {
|
||||
std::string first_arg = arg.first;
|
||||
if (first_arg == "git") {
|
||||
first_arg = "GIT_REPOSITORY";
|
||||
} else if (first_arg == "tag") {
|
||||
first_arg = "GIT_TAG";
|
||||
} else if (first_arg == "svn") {
|
||||
first_arg = "SVN_REPOSITORY";
|
||||
} else if (first_arg == "rev") {
|
||||
first_arg = "SVN_REVISION";
|
||||
} else if (first_arg == "url") {
|
||||
first_arg = "URL";
|
||||
} else if (first_arg == "hash") {
|
||||
first_arg = "URL_HASH";
|
||||
} else {
|
||||
// don't change arg
|
||||
}
|
||||
ss << "\t" << first_arg << "\n\t\t" << arg.second << "\n";
|
||||
}
|
||||
ss << ")\n";
|
||||
cmd("FetchContent_MakeAvailable")(dep.first).endl();
|
||||
}
|
||||
}
|
||||
|
||||
if (!cmake.vcpkg.version.empty()) {
|
||||
assert("pmm is required in fetch-content for vcpkg to work" && cmake.contents.count("pmm") != 0);
|
||||
comment("Bootstrap vcpkg");
|
||||
cmd("include")("${pmm_SOURCE_DIR}/pmm.cmake");
|
||||
tsl::ordered_map<std::string, std::vector<std::string>> vcpkg_args;
|
||||
vcpkg_args["REVISION"] = {cmake.vcpkg.version};
|
||||
vcpkg_args["REQUIRES"] = cmake.vcpkg.packages;
|
||||
auto vcpkg = std::make_pair("VCPKG", vcpkg_args);
|
||||
cmd("pmm")(vcpkg).endl();
|
||||
}
|
||||
|
||||
if (!cmake.packages.empty()) {
|
||||
comment("Packages");
|
||||
for (const auto &dep : cmake.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 (!cmake.options.empty()) {
|
||||
comment("Options");
|
||||
for (const auto &opt : cmake.options) {
|
||||
cmd("option")(opt.name, opt.comment, opt.val ? "ON" : "OFF");
|
||||
}
|
||||
endl();
|
||||
}
|
||||
|
||||
if (!cmake.settings.empty()) {
|
||||
comment("Settings");
|
||||
for (const auto &set : cmake.settings) {
|
||||
std::string set_val;
|
||||
if (set.val.index() == 1) {
|
||||
set_val = mpark::get<1>(set.val);
|
||||
} else {
|
||||
set_val = mpark::get<0>(set.val) ? "ON" : "OFF";
|
||||
}
|
||||
|
||||
if (set.cache) {
|
||||
auto typ = set.val.index() == 1 ? "STRING" : "BOOL";
|
||||
auto force = set.force ? "FORCE" : "";
|
||||
cmd("set")(set.name, set_val, typ, set.comment, force);
|
||||
} else {
|
||||
cmd("set")(set.name, set_val);
|
||||
}
|
||||
}
|
||||
endl();
|
||||
}
|
||||
|
||||
// generate_cmake is called on the subdirectories recursively later
|
||||
if (!cmake.subdirs.empty()) {
|
||||
gen.handle_condition(cmake.subdirs, [&](const std::string &, const std::vector<std::string> &subdirs) {
|
||||
for (const auto &dir : subdirs) {
|
||||
// clang-format off
|
||||
comment(dir);
|
||||
cmd("set")("CMKR_CMAKE_FOLDER", "${CMAKE_FOLDER}");
|
||||
cmd("if")("CMAKE_FOLDER");
|
||||
cmd("set")("CMAKE_FOLDER", "${CMAKE_FOLDER}/" + dir);
|
||||
cmd("else")();
|
||||
cmd("set")("CMAKE_FOLDER", dir);
|
||||
cmd("endif")();
|
||||
// clang-format on
|
||||
|
||||
cmd("add_subdirectory")(dir);
|
||||
cmd("set")("CMAKE_FOLDER", "${CMKR_CMAKE_FOLDER}").endl();
|
||||
}
|
||||
});
|
||||
endl();
|
||||
}
|
||||
|
||||
if (!cmake.targets.empty()) {
|
||||
for (const auto &target : cmake.targets) {
|
||||
comment("Target " + target.name);
|
||||
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); });
|
||||
|
||||
auto sources_var = target.name + "_SOURCES";
|
||||
|
||||
bool added_toml = false;
|
||||
cmd("set")(sources_var, RawArg("\"\"")).endl();
|
||||
gen.handle_condition(target.sources, [&](const std::string &condition, const std::vector<std::string> &condition_sources) {
|
||||
auto sources = expand_cmake_paths(condition_sources, path);
|
||||
if (sources.empty()) {
|
||||
auto source_key = condition.empty() ? "sources" : (condition + ".sources");
|
||||
throw std::runtime_error(target.name + " " + source_key + " wildcard found 0 files");
|
||||
}
|
||||
// Do not add cmake.toml twice
|
||||
if (!added_toml && std::find(sources.begin(), sources.end(), "cmake.toml") != sources.end()) {
|
||||
added_toml = true;
|
||||
}
|
||||
cmd("list")("APPEND", sources_var, sources);
|
||||
});
|
||||
|
||||
if (!added_toml && target.type != cmake::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_scope;
|
||||
switch (target.type) {
|
||||
case cmake::target_executable:
|
||||
add_command = "add_executable";
|
||||
target_type = "";
|
||||
target_scope = "PRIVATE";
|
||||
break;
|
||||
case cmake::target_library:
|
||||
add_command = "add_library";
|
||||
target_type = "";
|
||||
target_scope = "PUBLIC";
|
||||
break;
|
||||
case cmake::target_shared:
|
||||
add_command = "add_library";
|
||||
target_type = "SHARED";
|
||||
target_scope = "PUBLIC";
|
||||
break;
|
||||
case cmake::target_static:
|
||||
add_command = "add_library";
|
||||
target_type = "STATIC";
|
||||
target_scope = "PUBLIC";
|
||||
break;
|
||||
case cmake::target_interface:
|
||||
add_command = "add_library";
|
||||
target_type = "INTERFACE";
|
||||
target_scope = "INTERFACE";
|
||||
break;
|
||||
case cmake::target_custom:
|
||||
// TODO: add proper support, this is hacky
|
||||
add_command = "add_custom_target";
|
||||
target_type = "SOURCES";
|
||||
target_scope = "PUBLIC";
|
||||
break;
|
||||
default:
|
||||
assert("Unimplemented enum value" && false);
|
||||
}
|
||||
|
||||
cmd(add_command)(target.name, target_type).endl();
|
||||
|
||||
// clang-format off
|
||||
cmd("if")(sources_var);
|
||||
cmd("target_sources")(target.name, target.type == cmake::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 == cmake::target_executable) {
|
||||
cmd("get_directory_property")("CMKR_VS_STARTUP_PROJECT", "DIRECTORY", "${PROJECT_SOURCE_DIR}", "DEFINITION", "VS_STARTUP_PROJECT");
|
||||
// clang-format off
|
||||
cmd("if")("NOT", "CMKR_VS_STARTUP_PROJECT");
|
||||
cmd("set_property")("DIRECTORY", "${PROJECT_SOURCE_DIR}", "PROPERTY", "VS_STARTUP_PROJECT", target.name);
|
||||
cmd("endif")().endl();
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
if (!target.sources.empty()) {
|
||||
cmd("source_group")("TREE", "${CMAKE_CURRENT_SOURCE_DIR}", "FILES", "${" + target.name + "_SOURCES}").endl();
|
||||
}
|
||||
|
||||
if (!target.alias.empty()) {
|
||||
cmd("add_library")(target.alias, "ALIAS", target.name);
|
||||
}
|
||||
|
||||
auto target_cmd = [&](const char *command, const cmake::ConditionVector &cargs) {
|
||||
gen.handle_condition(
|
||||
cargs, [&](const std::string &, const std::vector<std::string> &args) { cmd(command)(target.name, target_scope, args); });
|
||||
};
|
||||
|
||||
target_cmd("target_compile_definitions", target.compile_definitions);
|
||||
target_cmd("target_compile_features", target.compile_features);
|
||||
target_cmd("target_compile_options", target.compile_options);
|
||||
target_cmd("target_include_directories", target.include_directories);
|
||||
target_cmd("target_link_directories", target.link_directories);
|
||||
target_cmd("target_link_libraries", target.link_libraries);
|
||||
target_cmd("target_precompile_headers", target.precompile_headers);
|
||||
|
||||
if (!target.properties.empty()) {
|
||||
cmd("set_target_properties")(target.name, "PROPERTIES", target.properties).endl();
|
||||
}
|
||||
|
||||
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); });
|
||||
|
||||
cmd("unset")("CMKR_TARGET");
|
||||
cmd("unset")("CMKR_SOURCES");
|
||||
}
|
||||
}
|
||||
|
||||
if (!cmake.tests.empty()) {
|
||||
cmd("enable_testing")().endl();
|
||||
for (const auto &test : cmake.tests) {
|
||||
auto name = std::make_pair("NAME", test.name);
|
||||
auto configurations = std::make_pair("CONFIGURATIONS", test.configurations);
|
||||
auto working_directory = std::make_pair("WORKING_DIRECTORY", test.working_directory);
|
||||
auto command = std::make_pair("COMMAND", test.command);
|
||||
auto arguments = std::make_pair("", test.arguments);
|
||||
cmd("add_test")(name, configurations, working_directory, command, arguments).endl();
|
||||
}
|
||||
}
|
||||
|
||||
if (!cmake.installs.empty()) {
|
||||
for (const auto &inst : cmake.installs) {
|
||||
auto targets = std::make_pair("TARGETS", inst.targets);
|
||||
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);
|
||||
if (files_data.empty()) {
|
||||
throw std::runtime_error("[[install]] files wildcard did not resolve to any files");
|
||||
}
|
||||
}
|
||||
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());
|
||||
cmd("install")(targets, dirs, files, configs, destination, component);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate CMakeLists.txt
|
||||
auto list_path = fs::path(path) / "CMakeLists.txt";
|
||||
|
||||
auto should_regenerate = [&list_path, &ss]() {
|
||||
if (!fs::exists(list_path))
|
||||
return true;
|
||||
|
||||
std::ifstream ifs(list_path, std::ios_base::binary);
|
||||
if (!ifs.is_open()) {
|
||||
throw std::runtime_error("Failed to read " + list_path.string());
|
||||
}
|
||||
|
||||
std::string data((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
|
||||
return data != ss.str();
|
||||
}();
|
||||
|
||||
if (should_regenerate) {
|
||||
std::ofstream ofs(list_path, std::ios_base::binary);
|
||||
if (ofs.is_open()) {
|
||||
ofs << ss.str();
|
||||
} else {
|
||||
throw std::runtime_error("Failed to write " + list_path.string());
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &itr : cmake.subdirs) {
|
||||
for (const auto &sub : itr.second) {
|
||||
auto subpath = fs::path(path) / fs::path(sub);
|
||||
if (fs::exists(subpath / "cmake.toml"))
|
||||
generate_cmake(subpath.string().c_str(), false);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
} // namespace gen
|
||||
} // namespace cmkr
|
||||
|
||||
int cmkr_gen_generate_project(const char *typ) {
|
||||
try {
|
||||
return cmkr::gen::generate_project(typ);
|
||||
} catch (const std::system_error &e) {
|
||||
return e.code().value();
|
||||
} catch (...) {
|
||||
return cmkr::error::Status(cmkr::error::Status::Code::InitError);
|
||||
}
|
||||
}
|
||||
|
||||
int cmkr_gen_generate_cmake(const char *path) {
|
||||
try {
|
||||
return cmkr::gen::generate_cmake(path);
|
||||
} catch (const std::system_error &e) {
|
||||
return e.code().value();
|
||||
} catch (...) {
|
||||
return cmkr::error::Status(cmkr::error::Status::Code::GenerationError);
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -1,9 +1,10 @@
|
||||
#include "help.hpp"
|
||||
#include <resources/version.hpp>
|
||||
|
||||
namespace cmkr {
|
||||
namespace help {
|
||||
|
||||
const char *version() noexcept { return "cmkr version 0.1.3"; }
|
||||
const char *version() noexcept { return "cmkr version " CMKR_VERSION; }
|
||||
|
||||
const char *message() noexcept {
|
||||
return R"lit(
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#include "args.hpp"
|
||||
#include "arguments.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
@@ -0,0 +1,597 @@
|
||||
#include "project_parser.hpp"
|
||||
|
||||
#include "fs.hpp"
|
||||
#include <deque>
|
||||
#include <stdexcept>
|
||||
#include <toml.hpp>
|
||||
#include <tsl/ordered_map.h>
|
||||
|
||||
namespace cmkr {
|
||||
namespace parser {
|
||||
|
||||
const char *targetTypeNames[target_last] = {"executable", "library", "shared", "static", "interface", "custom", "object", "template"};
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
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());
|
||||
auto line_width = line_number_str.length();
|
||||
auto line_str = loc.line_str();
|
||||
|
||||
std::ostringstream oss;
|
||||
oss << "[error] " << error << '\n';
|
||||
oss << " --> " << loc.file_name() << ':' << loc.line() << '\n';
|
||||
|
||||
oss << std::string(line_width + 2, ' ') << "|\n";
|
||||
oss << ' ' << line_number_str << " | " << line_str << '\n';
|
||||
|
||||
oss << std::string(line_width + 2, ' ') << '|';
|
||||
auto key_start = line_str.substr(0, loc.column() - 1).rfind(ky);
|
||||
if (key_start == std::string::npos) {
|
||||
key_start = line_str.find(ky);
|
||||
}
|
||||
if (key_start != std::string::npos) {
|
||||
oss << std::string(key_start + 1, ' ') << std::string(ky.length(), '~');
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
public:
|
||||
TomlChecker(const TomlBasicValue &v, const toml::key &ky) : m_v(toml::find(v, ky)) {}
|
||||
TomlChecker(const TomlBasicValue &v) : m_v(v) {}
|
||||
TomlChecker(const TomlChecker &) = delete;
|
||||
TomlChecker(TomlChecker &&) = delete;
|
||||
|
||||
template <typename T>
|
||||
void optional(const toml::key &ky, Condition<T> &destination) {
|
||||
// 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) {
|
||||
const auto &key = itr.first;
|
||||
const auto &value = itr.second;
|
||||
if (value.is_table()) {
|
||||
if (value.contains(ky)) {
|
||||
destination[key] = toml::find<T>(value, ky);
|
||||
}
|
||||
} else if (key == ky) {
|
||||
destination[""] = toml::find<T>(m_v, ky);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle visiting logic
|
||||
for (const auto &itr : destination) {
|
||||
if (!itr.first.empty()) {
|
||||
m_conditionVisited.emplace(itr.first, true);
|
||||
}
|
||||
}
|
||||
visit(ky);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void optional(const toml::key &ky, T &destination) {
|
||||
// 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);
|
||||
}
|
||||
visit(ky);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void required(const toml::key &ky, T &destination) {
|
||||
destination = toml::find<T>(m_v, ky);
|
||||
visit(ky);
|
||||
}
|
||||
|
||||
bool contains(const toml::key &ky) {
|
||||
visit(ky);
|
||||
return m_v.contains(ky);
|
||||
}
|
||||
|
||||
const TomlBasicValue &find(const toml::key &ky) {
|
||||
visit(ky);
|
||||
return toml::find(m_v, ky);
|
||||
}
|
||||
|
||||
void visit(const toml::key &ky) { m_visited.emplace(ky, true); }
|
||||
|
||||
void check(const tsl::ordered_map<std::string, std::string> &conditions) const {
|
||||
for (const auto &itr : m_v.as_table()) {
|
||||
const auto &ky = itr.first;
|
||||
if (m_conditionVisited.contains(ky)) {
|
||||
if (!conditions.contains(ky)) {
|
||||
throw std::runtime_error(format_key_error("Unknown condition '" + ky + "'", ky, itr.second));
|
||||
}
|
||||
|
||||
for (const auto &jtr : itr.second.as_table()) {
|
||||
if (!m_visited.contains(jtr.first)) {
|
||||
throw std::runtime_error(format_key_error("Unknown key '" + jtr.first + "'", jtr.first, jtr.second));
|
||||
}
|
||||
}
|
||||
} else if (!m_visited.contains(ky)) {
|
||||
if (itr.second.is_table()) {
|
||||
for (const auto &jtr : itr.second.as_table()) {
|
||||
if (!m_visited.contains(jtr.first)) {
|
||||
throw std::runtime_error(format_key_error("Unknown key '" + jtr.first + "'", jtr.first, jtr.second));
|
||||
}
|
||||
}
|
||||
}
|
||||
throw 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 {
|
||||
std::deque<TomlChecker> m_checkers;
|
||||
bool m_checked = false;
|
||||
|
||||
public:
|
||||
TomlCheckerRoot() = default;
|
||||
TomlCheckerRoot(const TomlCheckerRoot &) = delete;
|
||||
TomlCheckerRoot(TomlCheckerRoot &&) = delete;
|
||||
|
||||
TomlChecker &create(const TomlBasicValue &v) {
|
||||
m_checkers.emplace_back(v);
|
||||
return m_checkers.back();
|
||||
}
|
||||
|
||||
TomlChecker &create(const TomlBasicValue &v, const toml::key &ky) {
|
||||
m_checkers.emplace_back(v, ky);
|
||||
return m_checkers.back();
|
||||
}
|
||||
|
||||
void check(const tsl::ordered_map<std::string, std::string> &conditions) {
|
||||
for (const auto &checker : m_checkers) {
|
||||
checker.check(conditions);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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!");
|
||||
}
|
||||
const auto toml = toml::parse<toml::discard_comments, tsl::ordered_map, std::vector>(toml_path.string());
|
||||
|
||||
TomlCheckerRoot checker;
|
||||
|
||||
if (toml.contains("cmake")) {
|
||||
auto &cmake = checker.create(toml, "cmake");
|
||||
|
||||
cmake.required("version", cmake_version);
|
||||
|
||||
if (cmake.contains("bin-dir")) {
|
||||
throw std::runtime_error("bin-dir has been renamed to build-dir");
|
||||
}
|
||||
|
||||
cmake.optional("build-dir", build_dir);
|
||||
cmake.optional("generator", generator);
|
||||
cmake.optional("config", config);
|
||||
cmake.optional("arguments", gen_args);
|
||||
cmake.optional("allow-in-tree", allow_in_tree);
|
||||
|
||||
if (cmake.contains("cmkr-include")) {
|
||||
const auto &cmkr_include_kv = cmake.find("cmkr-include");
|
||||
if (cmkr_include_kv.is_string()) {
|
||||
cmkr_include = cmkr_include_kv.as_string();
|
||||
} else {
|
||||
// Allow disabling this feature with cmkr-include = false
|
||||
cmkr_include = "";
|
||||
}
|
||||
}
|
||||
|
||||
cmake.optional("cpp-flags", cppflags);
|
||||
cmake.optional("c-flags", cflags);
|
||||
cmake.optional("link-flags", linkflags);
|
||||
}
|
||||
|
||||
// Skip the rest of the parsing when building
|
||||
if (build) {
|
||||
checker.check(conditions);
|
||||
return;
|
||||
}
|
||||
|
||||
// Reasonable default conditions (you can override these if you desire)
|
||||
if (parent == nullptr) {
|
||||
conditions["windows"] = R"cmake(WIN32)cmake";
|
||||
conditions["macos"] = R"cmake(CMAKE_SYSTEM_NAME MATCHES "Darwin")cmake";
|
||||
conditions["unix"] = R"cmake(UNIX)cmake";
|
||||
conditions["bsd"] = R"cmake(CMAKE_SYSTEM_NAME MATCHES "BSD")cmake";
|
||||
conditions["linux"] = conditions["lunix"] = R"cmake(CMAKE_SYSTEM_NAME MATCHES "Linux")cmake";
|
||||
conditions["gcc"] = R"cmake(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "GNU")cmake";
|
||||
conditions["clang"] = R"cmake(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_C_COMPILER_ID MATCHES "Clang")cmake";
|
||||
conditions["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")) {
|
||||
auto conds = toml::find<decltype(conditions)>(toml, "conditions");
|
||||
for (const auto &cond : conds) {
|
||||
conditions[cond.first] = cond.second;
|
||||
}
|
||||
}
|
||||
|
||||
if (toml.contains("project")) {
|
||||
auto &project = checker.create(toml, "project");
|
||||
project.required("name", project_name);
|
||||
project.optional("version", project_version);
|
||||
project.optional("description", project_description);
|
||||
project.optional("languages", project_languages);
|
||||
project.optional("cmake-before", cmake_before);
|
||||
project.optional("cmake-after", cmake_after);
|
||||
project.optional("include-before", include_before);
|
||||
project.optional("include-after", include_after);
|
||||
project.optional("subdirs", project_subdirs);
|
||||
}
|
||||
|
||||
if (toml.contains("subdir")) {
|
||||
const auto &subs = toml::find(toml, "subdir").as_table();
|
||||
for (const auto &itr : subs) {
|
||||
Subdir subdir;
|
||||
subdir.name = itr.first;
|
||||
|
||||
auto &sub = checker.create(itr.second);
|
||||
sub.optional("condition", subdir.condition);
|
||||
sub.optional("cmake-before", subdir.cmake_before);
|
||||
sub.optional("cmake-after", subdir.cmake_after);
|
||||
sub.optional("include-before", subdir.include_before);
|
||||
sub.optional("include-after", subdir.include_after);
|
||||
|
||||
subdirs.push_back(subdir);
|
||||
}
|
||||
}
|
||||
|
||||
if (toml.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) {
|
||||
Setting s;
|
||||
s.name = itr.first;
|
||||
const auto &value = itr.second;
|
||||
if (value.is_boolean()) {
|
||||
s.val = value.as_boolean();
|
||||
} else if (value.is_string()) {
|
||||
s.val = value.as_string();
|
||||
} else {
|
||||
auto &setting = checker.create(value);
|
||||
setting.optional("comment", s.comment);
|
||||
if (setting.contains("value")) {
|
||||
const auto &v = setting.find("value");
|
||||
if (v.is_boolean()) {
|
||||
s.val = v.as_boolean();
|
||||
} else {
|
||||
s.val = v.as_string();
|
||||
}
|
||||
}
|
||||
setting.optional("cache", s.cache);
|
||||
setting.optional("force", s.force);
|
||||
}
|
||||
settings.push_back(s);
|
||||
}
|
||||
}
|
||||
|
||||
if (toml.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) {
|
||||
Option o;
|
||||
o.name = itr.first;
|
||||
const auto &value = itr.second;
|
||||
if (value.is_boolean()) {
|
||||
o.val = value.as_boolean();
|
||||
} else {
|
||||
auto &option = checker.create(value);
|
||||
option.optional("comment", o.comment);
|
||||
option.optional("value", o.val);
|
||||
}
|
||||
options.push_back(o);
|
||||
}
|
||||
}
|
||||
|
||||
if (toml.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;
|
||||
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);
|
||||
pkg.optional("components", p.components);
|
||||
}
|
||||
packages.push_back(p);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: perform checking here
|
||||
if (toml.contains("fetch-content")) {
|
||||
const auto &fc = toml::find(toml, "fetch-content").as_table();
|
||||
for (const auto &itr : fc) {
|
||||
Content content;
|
||||
content.name = itr.first;
|
||||
for (const auto &argItr : itr.second.as_table()) {
|
||||
auto key = argItr.first;
|
||||
if (key == "condition") {
|
||||
content.condition = argItr.second.as_string();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key == "git") {
|
||||
key = "GIT_REPOSITORY";
|
||||
} else if (key == "tag") {
|
||||
key = "GIT_TAG";
|
||||
} else if (key == "svn") {
|
||||
key = "SVN_REPOSITORY";
|
||||
} else if (key == "rev") {
|
||||
key = "SVN_REVISION";
|
||||
} else if (key == "url") {
|
||||
key = "URL";
|
||||
} else if (key == "hash") {
|
||||
key = "URL_HASH";
|
||||
} else {
|
||||
// don't change arg
|
||||
}
|
||||
content.arguments.emplace(key, argItr.second.as_string());
|
||||
}
|
||||
contents.emplace_back(std::move(content));
|
||||
}
|
||||
}
|
||||
|
||||
if (toml.contains("bin")) {
|
||||
throw std::runtime_error("[[bin]] has been renamed to [[target]]");
|
||||
}
|
||||
|
||||
auto parse_target = [&](const std::string &name, TomlChecker &t, bool isTemplate) {
|
||||
Target target;
|
||||
target.name = name;
|
||||
|
||||
t.required("type", target.type_name);
|
||||
target.type = parse_targetType(target.type_name);
|
||||
|
||||
// Users cannot set this target type
|
||||
if (target.type == target_template) {
|
||||
target.type = target_last;
|
||||
}
|
||||
|
||||
if (!isTemplate && target.type == target_last) {
|
||||
for (const auto &tmplate : templates) {
|
||||
if (target.type_name == tmplate.outline.name) {
|
||||
target.type = target_template;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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")));
|
||||
}
|
||||
|
||||
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 += ';';
|
||||
}
|
||||
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, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 (toml.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));
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &tmplate : templates) {
|
||||
if (name == tmplate.outline.name) {
|
||||
throw std::runtime_error(format_key_error("Template '" + name + "' already defined", name, itr.second));
|
||||
}
|
||||
}
|
||||
|
||||
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("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 (toml.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);
|
||||
t.optional("arguments", test.arguments);
|
||||
tests.push_back(test);
|
||||
}
|
||||
}
|
||||
|
||||
if (toml.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);
|
||||
installs.push_back(inst);
|
||||
}
|
||||
}
|
||||
|
||||
if (toml.contains("vcpkg")) {
|
||||
auto &v = checker.create(toml, "vcpkg");
|
||||
v.optional("url", vcpkg.url);
|
||||
v.optional("version", vcpkg.version);
|
||||
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);
|
||||
}
|
||||
|
||||
bool is_root_path(const std::string &path) {
|
||||
const auto toml_path = fs::path(path) / "cmake.toml";
|
||||
if (!fs::exists(toml_path)) {
|
||||
return false;
|
||||
}
|
||||
const auto toml = toml::parse<toml::discard_comments, tsl::ordered_map, std::vector>(toml_path.string());
|
||||
return toml.contains("project");
|
||||
}
|
||||
|
||||
} // namespace parser
|
||||
} // namespace cmkr
|
||||
+3
-2
@@ -1,2 +1,3 @@
|
||||
# These will be generated by cmkr, so no point in tracking them
|
||||
**/CMakeLists.txt
|
||||
# These will be generated by cmkr, so no point in tracking them
|
||||
**/CMakeLists.txt
|
||||
**/cmkr.cmake
|
||||
Generated
+52
-2
@@ -18,6 +18,26 @@ add_test(
|
||||
build
|
||||
)
|
||||
|
||||
add_test(
|
||||
NAME
|
||||
interface
|
||||
WORKING_DIRECTORY
|
||||
"${CMAKE_CURRENT_LIST_DIR}/interface"
|
||||
COMMAND
|
||||
$<TARGET_FILE:cmkr>
|
||||
build
|
||||
)
|
||||
|
||||
add_test(
|
||||
NAME
|
||||
fetch-content
|
||||
WORKING_DIRECTORY
|
||||
"${CMAKE_CURRENT_LIST_DIR}/fetch-content"
|
||||
COMMAND
|
||||
$<TARGET_FILE:cmkr>
|
||||
build
|
||||
)
|
||||
|
||||
add_test(
|
||||
NAME
|
||||
conditions
|
||||
@@ -30,9 +50,39 @@ add_test(
|
||||
|
||||
add_test(
|
||||
NAME
|
||||
interface
|
||||
vcpkg
|
||||
WORKING_DIRECTORY
|
||||
"${CMAKE_CURRENT_LIST_DIR}/interface"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/vcpkg"
|
||||
COMMAND
|
||||
$<TARGET_FILE:cmkr>
|
||||
build
|
||||
)
|
||||
|
||||
add_test(
|
||||
NAME
|
||||
cxx-standard
|
||||
WORKING_DIRECTORY
|
||||
"${CMAKE_CURRENT_LIST_DIR}/cxx-standard"
|
||||
COMMAND
|
||||
$<TARGET_FILE:cmkr>
|
||||
build
|
||||
)
|
||||
|
||||
add_test(
|
||||
NAME
|
||||
globbing
|
||||
WORKING_DIRECTORY
|
||||
"${CMAKE_CURRENT_LIST_DIR}/globbing"
|
||||
COMMAND
|
||||
$<TARGET_FILE:cmkr>
|
||||
build
|
||||
)
|
||||
|
||||
add_test(
|
||||
NAME
|
||||
templates
|
||||
WORKING_DIRECTORY
|
||||
"${CMAKE_CURRENT_LIST_DIR}/templates"
|
||||
COMMAND
|
||||
$<TARGET_FILE:cmkr>
|
||||
build
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
[cmake]
|
||||
version = "3.5"
|
||||
# A minimal `cmake.toml` project:
|
||||
|
||||
[project]
|
||||
name = "basic"
|
||||
description = "Minimal example"
|
||||
|
||||
[target.basic]
|
||||
type = "executable"
|
||||
sources = ["src/basic.cpp"]
|
||||
|
||||
# Declares an executable target called `basic` with `src/basic.cpp` as a source file. Equivalent to CMake's [add_executable](https://cmake.org/cmake/help/latest/command/add_executable.html)`(basic src/basic.cpp)`.
|
||||
+39
-9
@@ -1,17 +1,47 @@
|
||||
[[test]]
|
||||
name = "basic"
|
||||
working-directory = "basic"
|
||||
command = "$<TARGET_FILE:cmkr>"
|
||||
working-directory = "${CMAKE_CURRENT_LIST_DIR}/basic"
|
||||
arguments = ["build"]
|
||||
|
||||
[[test]]
|
||||
name = "conditions"
|
||||
command = "$<TARGET_FILE:cmkr>"
|
||||
working-directory = "${CMAKE_CURRENT_LIST_DIR}/conditions"
|
||||
arguments = ["build"]
|
||||
|
||||
[[test]]
|
||||
name = "interface"
|
||||
working-directory = "interface"
|
||||
command = "$<TARGET_FILE:cmkr>"
|
||||
working-directory = "${CMAKE_CURRENT_LIST_DIR}/interface"
|
||||
arguments = ["build"]
|
||||
arguments = ["build"]
|
||||
|
||||
[[test]]
|
||||
name = "fetch-content"
|
||||
working-directory = "fetch-content"
|
||||
command = "$<TARGET_FILE:cmkr>"
|
||||
arguments = ["build"]
|
||||
|
||||
[[test]]
|
||||
name = "conditions"
|
||||
working-directory = "conditions"
|
||||
command = "$<TARGET_FILE:cmkr>"
|
||||
arguments = ["build"]
|
||||
|
||||
[[test]]
|
||||
name = "vcpkg"
|
||||
working-directory = "vcpkg"
|
||||
command = "$<TARGET_FILE:cmkr>"
|
||||
arguments = ["build"]
|
||||
|
||||
[[test]]
|
||||
name = "cxx-standard"
|
||||
working-directory = "cxx-standard"
|
||||
command = "$<TARGET_FILE:cmkr>"
|
||||
arguments = ["build"]
|
||||
|
||||
[[test]]
|
||||
name = "globbing"
|
||||
working-directory = "globbing"
|
||||
command = "$<TARGET_FILE:cmkr>"
|
||||
arguments = ["build"]
|
||||
|
||||
[[test]]
|
||||
name = "templates"
|
||||
working-directory = "templates"
|
||||
command = "$<TARGET_FILE:cmkr>"
|
||||
arguments = ["build"]
|
||||
|
||||
@@ -14,4 +14,10 @@ windows.cmake-after = "message(STATUS win32-after)"
|
||||
macos.cmake-after = "message(STATUS macos-after)"
|
||||
linux.cmake-after = "message(STATUS linux-after)"
|
||||
unix.cmake-after = "message(STATUS unix-after)"
|
||||
custom.cmake-after = "message(STATUS custom-after)"
|
||||
custom.cmake-after = "message(STATUS custom-after)"
|
||||
|
||||
[target.example.properties]
|
||||
AUTOMOC = false
|
||||
custom.OUTPUT_NAME = "example2"
|
||||
custom.AUTORCC = true
|
||||
AUTOGEN = "ON"
|
||||
@@ -0,0 +1,12 @@
|
||||
# Require a C++11 compiler for the target `example`.
|
||||
|
||||
[project]
|
||||
name = "cxx-standard"
|
||||
description = "Changing C++ standard"
|
||||
|
||||
[target.example]
|
||||
type = "executable"
|
||||
sources = ["src/main.cpp"]
|
||||
compile-features = ["cxx_std_11"]
|
||||
|
||||
# This is equivalent to CMake's [target_compile_features](https://cmake.org/cmake/help/latest/command/target_compile_features.html)`(example PRIVATE cxx_std_11)`. For more information on available C/C++ standards and features see [cmake-compile-features(7)](https://cmake.org/cmake/help/latest/manual/cmake-compile-features.7.html).
|
||||
@@ -0,0 +1,8 @@
|
||||
#include <cstdio>
|
||||
#include <tuple>
|
||||
|
||||
int main()
|
||||
{
|
||||
auto tpl = std::make_tuple(1, 2);
|
||||
printf("Hello from C++11 %d\n", std::get<0>(tpl));
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
# Downloads [fmt v7.1.3](https://fmt.dev/7.1.3/) from [GitHub](https://github.com) and links an `example` target to it:
|
||||
|
||||
[project]
|
||||
name = "fetch-content"
|
||||
description = "Fetching from git"
|
||||
|
||||
[fetch-content]
|
||||
fmt = { git = "https://github.com/fmtlib/fmt", tag = "7.1.3" }
|
||||
|
||||
[target.example]
|
||||
type = "executable"
|
||||
sources = ["src/main.cpp"]
|
||||
link-libraries = ["fmt::fmt"]
|
||||
|
||||
# This is equivalent to calling CMake's [FetchContent](https://cmake.org/cmake/help/latest/module/FetchContent.html).
|
||||
@@ -0,0 +1,6 @@
|
||||
#include <fmt/core.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
fmt::print("Hello, world!\n");
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
[project]
|
||||
name = "globbing"
|
||||
description = "Globbing sources"
|
||||
|
||||
# Recursively glob in the mylib/ folder
|
||||
[target.mylib]
|
||||
type = "static"
|
||||
alias = "mylib::mylib"
|
||||
sources = ["mylib/**.hpp", "mylib/**.cpp"]
|
||||
include-directories = ["mylib/include"]
|
||||
|
||||
# Single-folder glob in example/src/
|
||||
[target.example]
|
||||
type = "executable"
|
||||
sources = ["example/src/*.cpp"]
|
||||
link-libraries = ["mylib::mylib"]
|
||||
|
||||
# As you can see in the example above you can use `**.ext` to glob recursively and `*.ext` to glob non-recursively. This **does not** generate `file(GLOB ...)` commands, but instead globs when cmkr is run. Files are sorted to give deterministic results regardless of the platform used.
|
||||
@@ -0,0 +1,7 @@
|
||||
#include <iostream>
|
||||
#include <mylib/mylib.hpp>
|
||||
|
||||
int main() {
|
||||
std::cout << mylib::message() << std::endl;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace mylib
|
||||
{
|
||||
std::string message();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#include <mylib/mylib.hpp>
|
||||
|
||||
std::string mylib::message()
|
||||
{
|
||||
return "cmkr is awesome!";
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
[project]
|
||||
name = "interface"
|
||||
description = "Header-only library"
|
||||
|
||||
[target.mylib]
|
||||
type = "interface"
|
||||
include-directories = ["include"]
|
||||
compile-features = ["cxx_std_11"]
|
||||
|
||||
[target.example]
|
||||
type = "executable"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
# Downloads [fmt v7.1.3](https://fmt.dev/7.1.3/) using [vcpkg](https://vcpkg.io/) and links an `example` target to it:
|
||||
|
||||
[project]
|
||||
name = "vcpkg"
|
||||
description = "Dependencies from vcpkg"
|
||||
|
||||
# See https://github.com/microsoft/vcpkg/releases for vcpkg versions
|
||||
# See https://vcpkg.io/en/packages.html for available packages
|
||||
[vcpkg]
|
||||
version = "2021.05.12"
|
||||
packages = ["fmt"]
|
||||
|
||||
[find-package]
|
||||
fmt = {}
|
||||
|
||||
[target.example]
|
||||
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`.
|
||||
# To specify package features you can use the following syntax: `imgui[docking-experimental,freetype,sdl2-binding,opengl3-binding]`.
|
||||
@@ -0,0 +1,6 @@
|
||||
#include <fmt/core.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
fmt::print("Hello, world!\n");
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"$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": [
|
||||
"fmt"
|
||||
],
|
||||
"description": "Dependencies from vcpkg",
|
||||
"name": "vcpkg",
|
||||
"version-string": ""
|
||||
}
|
||||
+4
@@ -16,3 +16,7 @@ target_include_directories(toml11 INTERFACE toml11-3.6.0)
|
||||
# https://github.com/mpark/variant (BSL-1.0)
|
||||
add_library(mpark_variant INTERFACE)
|
||||
target_include_directories(mpark_variant INTERFACE variant-1.4.0/include)
|
||||
|
||||
# https://github.com/nlohmann/json (MIT)
|
||||
add_library(nlohmann_json INTERFACE)
|
||||
target_include_directories(nlohmann_json INTERFACE nlohmann-3.9.1/include)
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2013-2021 Niels Lohmann
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+25447
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user