Compare commits

...

32 Commits

Author SHA1 Message Date
Duncan Ogilvie 83087ff06e Release v0.2.0 (finally) 2021-12-23 20:13:12 +01:00
Duncan Ogilvie 88e9241e9f Improve onboarding experience
Thanks Matthaus :give:
2021-12-23 18:04:54 +01:00
Duncan Ogilvie eff967277d Update README with a migration guide 2021-12-23 13:32:46 +01:00
Duncan Ogilvie ea01497246 Bump CMKR_TAG 2021-12-23 04:01:18 +01:00
Duncan Ogilvie 04bf40a5a6 Generate resources/version.hpp with the project version 2021-12-23 03:53:07 +01:00
Duncan Ogilvie 4d14395994 Implement support a CMKR_CACHE environment variable
Closes #17
2021-12-23 03:52:20 +01:00
Duncan Ogilvie 3dd3368e4e Build in Debug with CMAKE_UNITY_BUILD
This gives the fastest bootstrapping times and cmkr isn't noticeably slower during generation.
2021-12-23 01:56:11 +01:00
Duncan Ogilvie bb7874a6df Improve compilation times
Compilation with make went from 13 -> 10 seconds.
CMAKE_UNITY_BUILD went even lower to 6 seconds.

Closes #11
2021-12-23 01:54:57 +01:00
Duncan Ogilvie 91dbf2986e Remove dependency on nlohmann json 2021-12-23 00:55:20 +01:00
Duncan Ogilvie 2bcf15c4ed Add support for object libraries
Closes #34
2021-12-23 00:01:18 +01:00
Duncan Ogilvie 6a825e15a0 Slightly improve cmkr init #24 2021-12-22 18:29:13 +01:00
Duncan Ogilvie cc2b984aa5 Update documentation for conditions 2021-12-22 18:28:38 +01:00
Duncan Ogilvie 56a88c41e2 Merge pull request #35 from build-cpp/toml-checker
Proper error messages for invalid keys/conditions
2021-12-22 17:14:05 +01:00
Duncan Ogilvie 6809e8da41 Refactor and add checking for conditions 2021-12-22 16:51:55 +01:00
Duncan Ogilvie 9f9934e9a5 Implemented checking of keys in conditions 2021-12-22 13:31:52 +01:00
Duncan Ogilvie 77f4bf7da0 Some minor fixes related to newlines and formatting 2021-12-22 13:30:59 +01:00
Duncan Ogilvie 09c9c28934 Initial implementation of TomlChecker
This is to error when the user specifies an invalid key.

Closes #31
2021-12-19 02:34:48 +01:00
Duncan Ogilvie f02ccc2309 Refactor fetch-content to vector<Content> 2021-12-12 20:11:20 +01:00
Duncan Ogilvie 37e9a1f1a0 Merge pull request #33 from pmeerw/fix-linux
Ceating a directory with no name apparently fails on Linux, but silen…
2021-12-10 18:59:23 +01:00
Duncan Ogilvie a28e63fddc Merge pull request #32 from pmeerw/cleanup
Some cleanup
2021-12-10 18:59:09 +01:00
Peter Meerwald-Stadler 156fc961ec Ceating a directory with no name apparently fails on Linux, but silently succeeds on Windows 2021-12-10 17:39:36 +01:00
Peter Meerwald-Stadler 31cc1c9481 Avoid explicit number of error strings in cmkr_error_status() 2021-12-10 17:10:06 +01:00
Peter Meerwald-Stadler 19101cd726 Reduce scope of err_string; add missing error codes 2021-12-10 17:10:06 +01:00
Peter Meerwald-Stadler 8ebf69da7e Use C++ includes 2021-12-10 17:10:06 +01:00
Duncan Ogilvie 1c23469c8c Bump CMKR_TAG 2021-12-08 14:40:41 +01:00
Duncan Ogilvie 52671dcf5b Improve documentation for conditions 2021-12-08 14:39:48 +01:00
Duncan Ogilvie 094bab1b81 Add clarification that the system compiler is used when building cmkr 2021-12-08 14:39:35 +01:00
Duncan Ogilvie 0f559bc803 Do not make Package.config default to true 2021-12-08 14:39:18 +01:00
Duncan Ogilvie 491486075d Error when running cmkr.cmake as a script 2021-11-28 22:18:14 +01:00
Duncan Ogilvie ed84fc0251 Merge pull request #29 from build-cpp/fix-actions-pr
Fix a bug in the logic to skip building pull requests from the same repo
2021-10-27 18:11:24 +02:00
Duncan Ogilvie 8b6780fe1a Fix a bug in the logic to skip building pull requests from the same repository 2021-10-27 18:03:17 +02:00
Duncan Ogilvie 9ebcb1fb06 Bump CMKR_TAG 2021-10-24 16:19:50 +02:00
23 changed files with 910 additions and 583 deletions
+1
View File
@@ -0,0 +1 @@
github: [mrexodia]
+2 -2
View File
@@ -5,7 +5,7 @@ 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
@@ -23,7 +23,7 @@ jobs:
- 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 }}
+2 -1
View File
@@ -25,7 +25,7 @@ project(cmkr
LANGUAGES
CXX
VERSION
0.1.4
0.2.0
DESCRIPTION
"CMakeLists generator from TOML"
)
@@ -93,6 +93,7 @@ list(APPEND cmkr_SOURCES
"include/literals.hpp"
"include/project_parser.hpp"
"cmake/cmkr.cmake"
"cmake/version.hpp.in"
)
list(APPEND cmkr_SOURCES
+24 -5
View File
@@ -7,29 +7,48 @@
```toml
[project]
name = "cmkr_for_beginners"
description = "A minimal cmkr project."
[target.hello_world]
type = "executable"
sources = ["src/main.cpp"]
```
`cmkr` can bootstrap itself from CMake and consumers of your project do not need to install anything to work with it.
`cmkr` can bootstrap itself from CMake and you only need CMake to use it.
## Getting started
The easiest way to get started is to use the [cmkr_for_beginners](https://github.com/build-cpp/cmkr_for_beginners) template repository. Either open it in [Gitpod](https://gitpod.io/#https://github.com/build-cpp/cmkr_for_beginners), or clone the repository and run:
To get started run the following commands from your project directory:
```sh
curl https://raw.githubusercontent.com/build-cpp/cmkr/main/cmake/cmkr.cmake -o cmkr.cmake
cmake -P cmkr.cmake
```
After the bootstrapping process finishes, modify [`cmake.toml`](https://build-cpp.github.io/cmkr/cmake-toml) and open the project in your favorite IDE or build with CMake:
```sh
cmake -B build
cmake --build build
```
Alternatively you can check out the [cmkr topic](https://github.com/topics/cmkr), the [build-cpp organization](https://github.com/build-cpp) or the [tests](https://github.com/build-cpp/cmkr/tree/main/tests) for more examples and templates. You can also check out the [documentation](https://build-cpp.github.io/cmkr).
Once bootstrapped, `cmkr` does not introduce extra steps to your workflow. After modifying `cmake.toml` you simply build/configure your CMake project and `cmkr` will automatically regenerate `CMakeLists.txt`.
In CI settings the `cmkr` bootstrapping process is skipped so there is no extra configure-time overhead in your pipelines.
## Template repositories
Another way to get started is to use the [cmkr_for_beginners](https://github.com/build-cpp/cmkr_for_beginners) template repository. Either open it in [Gitpod](https://gitpod.io/#https://github.com/build-cpp/cmkr_for_beginners), or clone the repository and run:
```sh
cmake -B build
cmake --build build
```
Check out the [cmkr topic](https://github.com/topics/cmkr), the [build-cpp organization](https://github.com/build-cpp) or the [tests](https://github.com/build-cpp/cmkr/tree/main/tests) for more examples and templates.
## Command line
Optionally you can install `cmkr` in your `PATH` and use it as a utility from the command line:
Optionally you can put a [`cmkr` release](https://github.com/build-cpp/cmkr/releases) in your `PATH` and use it as a utility from the command line:
```
Usage: cmkr [arguments]
+3 -2
View File
@@ -4,7 +4,7 @@ cmkr-include = false
[project]
name = "cmkr"
version = "0.1.4"
version = "0.2.0"
description = "CMakeLists generator from TOML"
languages = ["CXX"]
subdirs = ["third_party", "tests"]
@@ -25,6 +25,7 @@ sources = [
"src/*.cpp",
"include/*.hpp",
"cmake/cmkr.cmake",
"cmake/version.hpp.in",
]
include-directories = [
"include",
@@ -35,7 +36,7 @@ link-libraries = [
"ghc_filesystem",
"mpark_variant",
"ordered_map",
"nlohmann_json"
"nlohmann_json",
]
cmake-after = """
generate_resources(${CMKR_TARGET})
+69 -10
View File
@@ -1,12 +1,20 @@
include_guard()
# 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()
# 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_a718dfd6" CACHE STRING "cmkr git tag (this needs to be available forever)" FORCE)
set(CMKR_TAG "v0.2.0" 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")
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)
@@ -18,7 +26,7 @@ if(DEFINED ENV{CI} OR CMKR_SKIP_GENERATION OR CMKR_BUILD_SKIP_GENERATION)
endif()
# Disable cmkr if no cmake.toml file is found
if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake.toml")
if(NOT CMAKE_SCRIPT_MODE_FILE AND NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake.toml")
message(AUTHOR_WARNING "[cmkr] Not found: ${CMAKE_CURRENT_SOURCE_DIR}/cmake.toml")
macro(cmkr)
endmacro()
@@ -48,18 +56,45 @@ else()
endif()
# Use cached cmkr if found
set(CMKR_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/_cmkr_${CMKR_TAG}")
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}")
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)
# 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}'")
@@ -77,23 +112,24 @@ else()
${CMKR_REPO}
"${CMKR_DIRECTORY}"
)
message(STATUS "[cmkr] Building cmkr...")
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=Release"
"-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 Release
--config "${CMKR_BUILD_TYPE}"
--parallel
)
cmkr_exec("${CMAKE_COMMAND}"
--install "${CMKR_DIRECTORY}/build"
--config Release
--config "${CMKR_BUILD_TYPE}"
--prefix "${CMKR_DIRECTORY}"
--component cmkr
)
@@ -110,6 +146,29 @@ if(NOT CMKR_EXEC_RESULT EQUAL 0)
message(FATAL_ERROR "[cmkr] Failed to get version, try clearing the cache and rebuilding")
endif()
# Use cmkr.cmake as a script
if(CMAKE_SCRIPT_MODE_FILE)
if(NOT EXISTS "${CMAKE_SOURCE_DIR}/cmake.toml")
execute_process(COMMAND "${CMKR_EXECUTABLE}" init
RESULT_VARIABLE CMKR_EXEC_RESULT
)
if(NOT CMKR_EXEC_RESULT EQUAL 0)
message(FATAL_ERROR "[cmkr] Failed to bootstrap cmkr project. Please report an issue: https://github.com/build-cpp/cmkr/issues/new")
else()
message(STATUS "[cmkr] Modify cmake.toml and then configure using: cmake -B build")
endif()
else()
execute_process(COMMAND "${CMKR_EXECUTABLE}" gen
RESULT_VARIABLE CMKR_EXEC_RESULT
)
if(NOT CMKR_EXEC_RESULT EQUAL 0)
message(FATAL_ERROR "[cmkr] Failed to generate project.")
else()
message(STATUS "[cmkr] Configure using: cmake -B build")
endif()
endif()
endif()
# This is the macro that contains black magic
macro(cmkr)
# When this macro is called from the generated file, fake some internal CMake variables
+6 -2
View File
@@ -5,15 +5,19 @@ function(generate_resources 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$")
get_filename_component(RESOURCE_NAME "${SOURCE}" NAME_WE)
set(RESOURCE_HEADER "include/resources/${RESOURCE_NAME}.hpp")
# 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")
+3
View File
@@ -0,0 +1,3 @@
#pragma once
#define CMKR_VERSION "@PROJECT_VERSION@"
+32 -7
View File
@@ -1,15 +1,15 @@
---
layout: page
title: cmake.toml
title: Reference
permalink: /cmake-toml/
nav_order: 3
nav_order: 1
---
# cmake.toml
# Reference
This page is supposed to be a reference for the options in the `cmake.toml` file. If you think anything is missing or unclear, please [edit this page](https://github.com/build-cpp/cmkr/edit/main/docs/cmake-toml.md) or open an [issue](https://github.com/build-cpp/cmkr/issues).
This page is a reference for the options in the `cmake.toml` file. If you think anything is missing or unclear, please [edit this page](https://github.com/build-cpp/cmkr/edit/main/docs/cmake-toml.md) or open an [issue](https://github.com/build-cpp/cmkr/issues).
See the [examples](/examples) section for more real-world examples.
See the [examples](/examples) section for concrete examples.
## CMake configuration
@@ -39,12 +39,37 @@ 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"
```
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"]
```
This will make the `arch64` and `arch32` conditions available with their respective CMake expressions. The following conditions are predefined (you can override them if you desire):
```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"
```
## Subdirectories
```toml
@@ -69,7 +94,7 @@ 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. For a custom registry you can specify your own `url` (and omit the `version`).
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).
## Packages
@@ -107,7 +132,7 @@ tag = "v0.1"
[target.mytarget]
condition = "linux"
alias = "mytarget::mytarget"
type = "static" # executable, library, shared, static, interface, custom
type = "static" # executable, shared (DLL), static, interface, object, library, custom
headers = ["src/mytarget.h"]
sources = ["src/mytarget.cpp"]
-22
View File
@@ -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.
```
-17
View File
@@ -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.
+1
View File
@@ -0,0 +1 @@
google-site-verification: google13f7c659b54c069f.html
+57 -3
View File
@@ -6,18 +6,72 @@ nav_order: 0
# Index
`cmkr`, pronounced "cmaker", is a modern build system based on [CMake](https://cmake.org/) and [TOML](https://toml.io). It was originally created by [Mohammed Alyousef](https://github.com/MoAlyousef).
`cmkr`, pronounced "cmaker", is a modern build system based on [CMake](https://cmake.org/) and [TOML](https://toml.io).
`cmkr` parses `cmake.toml` files and generates a modern, idiomatic `CMakeLists.txt` for you. A minimal example:
```toml
[project]
name = "cmkr_for_beginners"
description = "A minimal cmkr project."
[target.hello_world]
type = "executable"
sources = ["src/main.cpp"]
```
`cmkr` can bootstrap itself from CMake and consumers of your project do not need to install anything to work with it.
`cmkr` can bootstrap itself from CMake and you only need CMake to use it.
## Getting started
To get started run the following commands from your project directory:
```sh
curl https://raw.githubusercontent.com/build-cpp/cmkr/main/cmake/cmkr.cmake -o cmkr.cmake
cmake -P cmkr.cmake
```
After the bootstrapping process finishes, modify [`cmake.toml`](https://build-cpp.github.io/cmkr/cmake-toml) and open the project in your favorite IDE or build with CMake:
```sh
cmake -B build
cmake --build build
```
Once bootstrapped, `cmkr` does not introduce extra steps to your workflow. After modifying `cmake.toml` you simply build/configure your CMake project and `cmkr` will automatically regenerate `CMakeLists.txt`.
In CI settings the `cmkr` bootstrapping process is skipped so there is no extra configure-time overhead in your pipelines.
## Template repositories
Another way to get started is to use the [cmkr_for_beginners](https://github.com/build-cpp/cmkr_for_beginners) template repository. Either open it in [Gitpod](https://gitpod.io/#https://github.com/build-cpp/cmkr_for_beginners), or clone the repository and run:
```sh
cmake -B build
cmake --build build
```
Check out the [cmkr topic](https://github.com/topics/cmkr), the [build-cpp organization](https://github.com/build-cpp) or the [tests](https://github.com/build-cpp/cmkr/tree/main/tests) for more examples and templates.
## Command line
Optionally you can put a [`cmkr` release](https://github.com/build-cpp/cmkr/releases) in your `PATH` and use it as a utility from the command line:
```
Usage: cmkr [arguments]
arguments:
init [executable|library|shared|static|interface] Starts a new project in the same directory.
gen Generates CMakeLists.txt file.
build <extra cmake args> Run cmake and build.
install Run cmake --install. Needs admin privileges.
clean Clean the build directory.
help Show help.
version Current cmkr version.
```
## Credits
- https://github.com/gulrak/filesystem
- https://github.com/Tessil/ordered-map
- https://github.com/ToruNiina/toml11
- https://github.com/mpark/variant
- https://www.svgrepo.com/svg/192268/hammer
+2 -6
View File
@@ -5,13 +5,9 @@
namespace cmkr {
namespace gen {
int generate_project(const char *typ);
void generate_project(const std::string &type);
int generate_cmake(const char *path, const parser::Project *parent_project = nullptr);
void generate_cmake(const char *path, const parser::Project *parent_project = nullptr);
} // namespace gen
} // namespace cmkr
int cmkr_gen_generate_project(const char *typ);
int cmkr_gen_generate_cmake(const char *path);
+88 -33
View File
@@ -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
+8 -4
View File
@@ -1,8 +1,6 @@
#pragma once
#include <map>
#include <mpark/variant.hpp>
#include <stdexcept>
#include <string>
#include <tsl/ordered_map.h>
#include <vector>
@@ -28,7 +26,7 @@ struct Package {
std::string name;
std::string version;
bool required = true;
bool config = true;
bool config = false;
std::vector<std::string> components;
};
@@ -45,6 +43,7 @@ enum TargetType {
target_static,
target_interface,
target_custom,
target_object,
};
template <typename T>
@@ -119,6 +118,11 @@ struct Subdir {
ConditionVector include_after;
};
struct Content {
std::string name;
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";
@@ -145,7 +149,7 @@ struct Project {
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<Target> targets;
std::vector<Test> tests;
std::vector<Install> installs;
+4 -10
View File
@@ -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,18 +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!";
ret = cmkr::gen::generate_cmake(fs::current_path().string().c_str());
if (ret)
return "CMake generation error!";
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)
+5 -6
View File
@@ -4,10 +4,10 @@
#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 {
@@ -22,10 +22,9 @@ int run(int argc, char **argv) {
}
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. -DCMKR_BUILD_SKIP_GENERATION=ON -B" << project.build_dir << " ";
ss << "cmake -DCMKR_BUILD_SKIP_GENERATION=ON -B" << project.build_dir << " ";
if (!project.generator.empty()) {
ss << "-G \"" << project.generator << "\" ";
@@ -94,4 +93,4 @@ int cmkr_build_install(void) {
} catch (...) {
return cmkr::error::Status(cmkr::error::Status::Code::InstallError);
}
}
}
+133 -143
View File
@@ -4,22 +4,15 @@
#include <resources/cmkr.hpp>
#include "fs.hpp"
#include <cassert>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <new>
#include <nlohmann/json.hpp>
#include <regex>
#include <sstream>
#include <stdexcept>
#include <string>
namespace cmkr {
namespace gen {
inline std::string to_upper(const std::string &str) {
static std::string to_upper(const std::string &str) {
std::string temp;
temp.reserve(str.size());
for (auto c : str) {
@@ -28,16 +21,16 @@ inline std::string to_upper(const std::string &str) {
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::string format(const char *format, tsl::ordered_map<std::string, std::string> variables) {
std::string s = format;
for (const auto &itr : variables) {
size_t start_pos = 0;
while ((start_pos = s.find(itr.first, start_pos)) != std::string::npos) {
s.replace(start_pos, itr.first.length(), itr.second);
start_pos += itr.second.length();
}
}
return s;
}
static std::vector<std::string> expand_cmake_path(const fs::path &name, const fs::path &toml_dir) {
@@ -89,52 +82,50 @@ static std::vector<std::string> expand_cmake_paths(const std::vector<std::string
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;
static void create_file(const fs::path &path, const std::string &contents) {
if (!path.parent_path().empty()) {
fs::create_directories(path.parent_path());
}
std::ofstream ofs(path, std::ios::binary);
if (!ofs) {
throw std::runtime_error("Failed to create " + path.string());
}
ofs << contents;
}
void generate_project(const std::string &type) {
const auto name = fs::current_path().stem().string();
if (fs::exists(fs::current_path() / "cmake.toml")) {
throw std::runtime_error("Cannot initialize a project when cmake.toml already exists!");
}
tsl::ordered_map<std::string, std::string> variables = {
{"@name", name},
{"@type", type},
};
if (!fs::is_empty(fs::current_path())) {
// Make a backup of an existing CMakeLists.txt if it exists
std::error_code ec;
fs::rename("CMakeLists.txt", "CMakeLists.txt.bak", ec);
// Create an empty cmake.toml for migration purporses
create_file("cmake.toml", format(toml_migration, variables));
return;
}
if (type == "executable") {
create_file("cmake.toml", format(toml_executable, variables));
create_file("src/" + name + "/main.cpp", format(cpp_executable, variables));
} else if (type == "static" || type == "shared" || type == "library") {
create_file("cmake.toml", format(toml_library, variables));
create_file("src/" + name + "/" + name + ".cpp", format(cpp_library, variables));
create_file("include/" + name + "/" + name + ".hpp", format(hpp_library, variables));
} else if (type == "interface") {
create_file("cmake.toml", format(toml_interface, variables));
create_file("include/" + name + "/" + name + ".hpp", format(hpp_interface, variables));
} else {
throw std::runtime_error("Unknown project type " + std::string(str) +
"! Supported types are: executable, library, shared, static, interface");
throw std::runtime_error("Unknown project type " + type + "! 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 {
@@ -163,13 +154,13 @@ struct Command {
Command(std::stringstream &ss, int depth, std::string command, std::string post_comment)
: ss(ss), depth(depth), command(std::move(command)), post_comment(std::move(post_comment)) {}
~Command() {
~Command() noexcept(false) {
if (!generated) {
assert(false && "Incorrect usage of cmd(), you probably forgot ()");
throw std::runtime_error("Incorrect usage of cmd(), you probably forgot ()");
}
}
std::string quote(const std::string &str) {
static 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) {
@@ -413,10 +404,28 @@ struct Generator {
};
static bool vcpkg_valid_identifier(const std::string &name) {
const std::regex reserved("prn|aux|nul|con|lpt[1-9]|com[1-9]|core|default");
const std::regex ok("[a-z0-9]+(-[a-z0-9]+)*");
std::cmatch m;
return !std::regex_match(name.c_str(), m, reserved) && std::regex_match(name.c_str(), m, ok);
// prn|aux|nul|con|lpt[1-9]|com[1-9]|core|default
auto is_reserved = [](const std::string &s) {
if (s == "prn" || s == "aux" || s == "nul" || s == "con" || s == "core" || s == "default") {
return true;
}
if (s.length() == 4 && (s.compare(0, 3, "lpt") == 0 || s.compare(0, 3, "com") == 0) && (s[3] >= '1' && s[3] <= '9')) {
return true;
}
return false;
};
// [a-z0-9]+(-[a-z0-9]+)*
auto is_identifier = [](const std::string &s) {
for (size_t i = 0; i < s.length(); i++) {
auto c = s[i];
if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (i > 0 && c == '-')) {
continue;
}
return false;
}
return true;
};
return is_identifier(name) && !is_reserved(name);
}
static std::string vcpkg_escape_identifier(const std::string &name) {
@@ -440,7 +449,7 @@ static std::string vcpkg_escape_identifier(const std::string &name) {
return escaped;
}
int generate_cmake(const char *path, const parser::Project *parent_project) {
void generate_cmake(const char *path, const parser::Project *parent_project) {
if (!fs::exists(fs::path(path) / "cmake.toml")) {
throw std::runtime_error("No cmake.toml found!");
}
@@ -494,12 +503,7 @@ int generate_cmake(const char *path, const parser::Project *parent_project) {
fs::path cmkr_include(project.cmkr_include);
if (!project.cmkr_include.empty() && !fs::exists(cmkr_include) && cmkr_include.is_relative()) {
fs::create_directories(cmkr_include.parent_path());
std::ofstream ofs(cmkr_include.string(), std::ios::binary);
if (!ofs) {
throw std::runtime_error("Failed to create " + project.cmkr_include);
}
ofs.write(resources::cmkr, strlen(resources::cmkr));
create_file(cmkr_include, resources::cmkr);
}
}
@@ -552,30 +556,14 @@ int generate_cmake(const char *path, const parser::Project *parent_project) {
if (!project.contents.empty()) {
cmd("include")("FetchContent").endl();
for (const auto &dep : project.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";
for (const auto &content : project.contents) {
cmd("message")("STATUS", "Fetching " + content.name + "...");
ss << "FetchContent_Declare(\n\t" << content.name << "\n";
for (const auto &arg : content.arguments) {
ss << "\t" << arg.first << "\n\t\t" << arg.second << "\n";
}
ss << ")\n";
cmd("FetchContent_MakeAvailable")(dep.first).endl();
cmd("FetchContent_MakeAvailable")(content.name).endl();
}
}
@@ -608,24 +596,47 @@ int generate_cmake(const char *path, const parser::Project *parent_project) {
endl();
// clang-format on
// Generate vcpkg.json
using namespace nlohmann;
json j;
j["$cmkr"] = "This file is automatically generated from cmake.toml - DO NOT EDIT";
j["$cmkr-url"] = cmkr_url;
j["$schema"] = "https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json";
j["name"] = vcpkg_escape_identifier(project.project_name);
j["version-string"] = project.project_version;
j["dependencies"] = project.vcpkg.packages;
if (!project.project_description.empty()) {
j["description"] = project.project_description;
}
std::ofstream ofs("vcpkg.json");
// Generate vcpkg.json (sorry for the ugly string handling, nlohmann compiles very slowly)
std::ofstream ofs("vcpkg.json", std::ios::binary);
if (!ofs) {
throw std::runtime_error("Failed to create a vcpkg.json manifest file!");
}
ofs << std::setw(2) << j << std::endl;
ofs << R"({
"$cmkr": "This file is automatically generated from cmake.toml - DO NOT EDIT",
"$cmkr-url": "https://github.com/build-cpp/cmkr",
"$schema": "https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json",
"dependencies": [
)";
const auto &packages = project.vcpkg.packages;
for (size_t i = 0; i < packages.size(); i++) {
const auto &package = packages[i];
if (!vcpkg_valid_identifier(package)) {
throw std::runtime_error("Invalid vcpkg package name '" + package + "'");
}
ofs << " \"" << package << '\"';
if (i + 1 < packages.size()) {
ofs << ',';
}
ofs << '\n';
}
auto escape = [](const std::string &str) {
std::string result;
for (auto ch : str) {
if (ch == '\\' || ch == '\"') {
result += '\\';
}
result += ch;
}
return result;
};
ofs << " ],\n";
ofs << " \"description\": \"" << escape(project.project_description) << "\",\n";
ofs << " \"name\": \"" << escape(project.project_name) << "\",\n";
ofs << R"( "version-string": "")" << '\n';
ofs << "}\n";
}
if (!project.packages.empty()) {
@@ -792,8 +803,14 @@ int generate_cmake(const char *path, const parser::Project *parent_project) {
target_type = "SOURCES";
target_scope = "PUBLIC";
break;
case parser::target_object:
// NOTE: This is properly supported since 3.12
add_command = "add_library";
target_type = "OBJECT";
target_scope = "PUBLIC";
break;
default:
assert("Unimplemented enum value" && false);
throw std::runtime_error("Unimplemented enum value");
}
cmd(add_command)(target.name, target_type).endl();
@@ -914,7 +931,7 @@ int generate_cmake(const char *path, const parser::Project *parent_project) {
if (!fs::exists(list_path))
return true;
std::ifstream ifs(list_path, std::ios_base::binary);
std::ifstream ifs(list_path, std::ios::binary);
if (!ifs.is_open()) {
throw std::runtime_error("Failed to read " + list_path.string());
}
@@ -924,12 +941,7 @@ int generate_cmake(const char *path, const parser::Project *parent_project) {
}();
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());
}
create_file(list_path, ss.str());
}
auto generate_subdir = [path, &project](const fs::path &sub) {
@@ -955,28 +967,6 @@ int generate_cmake(const char *path, const parser::Project *parent_project) {
for (const auto &subdir : project.subdirs) {
generate_subdir(subdir.name);
}
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);
}
}
+6 -5
View File
@@ -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];
}
}
+2 -1
View File
@@ -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(
+451 -293
View File
@@ -2,17 +2,18 @@
#include "enum_helper.hpp"
#include "fs.hpp"
#include <deque>
#include <stdexcept>
#include <toml.hpp>
#include <tsl/ordered_map.h>
template <>
const char *enumStrings<cmkr::parser::TargetType>::data[] = {"executable", "library", "shared", "static", "interface", "custom"};
const char *enumStrings<cmkr::parser::TargetType>::data[] = {"executable", "library", "shared", "static", "interface", "custom", "object"};
namespace cmkr {
namespace parser {
using TomlBasicValue = toml::basic_value<toml::preserve_comments, tsl::ordered_map, std::vector>;
using TomlBasicValue = toml::basic_value<toml::discard_comments, tsl::ordered_map, std::vector>;
template <typename EnumType>
static EnumType to_enum(const std::string &str, const std::string &help_name) {
@@ -33,314 +34,471 @@ static EnumType to_enum(const std::string &str, const std::string &help_name) {
return value;
}
template <typename T>
static void get_optional(const TomlBasicValue &v, const toml::key &ky, T &destination);
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();
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);
std::ostringstream oss;
oss << "[error] " << error << '\n';
oss << " --> " << loc.file_name() << '\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(), '~');
}
oss << '\n';
return oss.str();
}
class TomlChecker {
const TomlBasicValue &m_v;
tsl::ordered_map<toml::key, bool> m_visited;
tsl::ordered_map<toml::key, bool> m_conditionVisited;
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 (key == ky) {
destination[""] = toml::find<T>(v, ky);
}
}
}
};
template <typename T>
static void get_optional(const TomlBasicValue &v, const toml::key &ky, T &destination) {
// TODO: this currently doesn't allow you to get an optional map<string, X>
if (v.contains(ky)) {
destination = toml::find<T>(v, ky);
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::preserve_comments, tsl::ordered_map, std::vector>(toml_path.string());
const auto toml = toml::parse<toml::discard_comments, tsl::ordered_map, std::vector>(toml_path.string());
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) {
if (toml.contains("cmake")) {
const auto &cmake = toml::find(toml, "cmake");
checker.check(conditions);
return;
}
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);
get_optional(cmake, "allow-in-tree", allow_in_tree);
}
// 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";
} else {
if (toml.contains("cmake")) {
const auto &cmake = toml::find(toml, "cmake");
cmake_version = toml::find(cmake, "version").as_string();
if (cmake.contains("cmkr-include")) {
const auto &cmkr_include_kv = toml::find(cmake, "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 = "";
}
}
get_optional(cmake, "cpp-flags", cppflags);
get_optional(cmake, "c-flags", cflags);
get_optional(cmake, "link-flags", linkflags);
}
conditions = parent->conditions;
}
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", project_subdirs);
}
if (toml.contains("subdir")) {
const auto &subs = toml::find(toml, "subdir").as_table();
for (const auto &sub : subs) {
Subdir subdir;
subdir.name = sub.first;
get_optional(sub.second, "condition", subdir.condition);
get_optional(sub.second, "cmake-before", subdir.cmake_before);
get_optional(sub.second, "cmake-after", subdir.cmake_after);
get_optional(sub.second, "include-before", subdir.include_before);
get_optional(sub.second, "include-after", subdir.include_after);
subdirs.push_back(subdir);
}
}
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?
if (toml.contains("fetch-content")) {
contents = toml::find<decltype(contents)>(toml, "fetch-content");
}
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, "headers", target.headers);
get_optional(t, "sources", target.sources);
get_optional(t, "compile-definitions", target.compile_definitions);
get_optional(t, "private-compile-definitions", target.private_compile_definitions);
get_optional(t, "compile-features", target.compile_features);
get_optional(t, "private-compile-features", target.private_compile_features);
get_optional(t, "compile-options", target.compile_options);
get_optional(t, "private-compile-options", target.private_compile_options);
get_optional(t, "include-directories", target.include_directories);
get_optional(t, "private-include-directories", target.private_include_directories);
get_optional(t, "link-directories", target.link_directories);
get_optional(t, "private-link-directories", target.private_link_directories);
get_optional(t, "link-libraries", target.link_libraries);
get_optional(t, "private-link-libraries", target.private_link_libraries);
get_optional(t, "link-options", target.link_options);
get_optional(t, "private-link-options", target.private_link_options);
get_optional(t, "precompile-headers", target.precompile_headers);
get_optional(t, "private-precompile-headers", target.private_precompile_headers);
if (!target.headers.empty()) {
auto &sources = target.sources.nth(0).value();
const auto &headers = target.headers.nth(0)->second;
sources.insert(sources.end(), headers.begin(), headers.end());
}
if (t.contains("condition")) {
target.condition = toml::find(t, "condition").as_string();
}
if (t.contains("alias")) {
target.alias = toml::find(t, "alias").as_string();
}
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 = toml::find(t, "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, "");
}
}
}
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");
get_optional(v, "url", vcpkg.url);
get_optional(v, "version", vcpkg.version);
vcpkg.packages = toml::find<decltype(vcpkg.packages)>(v, "packages");
}
// 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";
} else {
conditions = parent->conditions;
}
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("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("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 == "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]]");
}
if (toml.contains("target")) {
const auto &ts = toml::find(toml, "target").as_table();
for (const auto &itr : ts) {
const auto &value = itr.second;
Target target;
target.name = itr.first;
auto &t = checker.create(value);
std::string type;
t.required("type", type);
target.type = to_enum<TargetType>(type, "target type");
t.optional("headers", target.headers);
t.optional("sources", target.sources);
t.optional("compile-definitions", target.compile_definitions);
t.optional("private-compile-definitions", target.private_compile_definitions);
t.optional("compile-features", target.compile_features);
t.optional("private-compile-features", target.private_compile_features);
t.optional("compile-options", target.compile_options);
t.optional("private-compile-options", target.private_compile_options);
t.optional("include-directories", target.include_directories);
t.optional("private-include-directories", target.private_include_directories);
t.optional("link-directories", target.link_directories);
t.optional("private-link-directories", target.private_link_directories);
t.optional("link-libraries", target.link_libraries);
t.optional("private-link-libraries", target.private_link_libraries);
t.optional("link-options", target.link_options);
t.optional("private-link-options", target.private_link_options);
t.optional("precompile-headers", target.precompile_headers);
t.optional("private-precompile-headers", target.private_precompile_headers);
if (!target.headers.empty()) {
auto &sources = target.sources.nth(0).value();
const auto &headers = target.headers.nth(0)->second;
sources.insert(sources.end(), headers.begin(), headers.end());
}
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);
targets.push_back(target);
}
}
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("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("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);
v.required("packages", vcpkg.packages);
}
checker.check(conditions);
}
bool is_root_path(const std::string &path) {
@@ -348,7 +506,7 @@ bool is_root_path(const std::string &path) {
if (!fs::exists(toml_path)) {
return false;
}
const auto toml = toml::parse<toml::preserve_comments, tsl::ordered_map, std::vector>(toml_path.string());
const auto toml = toml::parse<toml::discard_comments, tsl::ordered_map, std::vector>(toml_path.string());
return toml.contains("project");
}
+11 -11
View File
@@ -1,11 +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": ""
}
{
"$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": ""
}