Compare commits

...

67 Commits

Author SHA1 Message Date
Duncan Ogilvie 58c7de1d60
Merge pull request #129 from build-cpp/gitignore-improvement
1 year ago
Duncan Ogilvie a61e4bb999
Merge pull request #128 from build-cpp/language-documentation
1 year ago
Duncan Ogilvie 47aaeaf8ce Document the supported languages
1 year ago
Duncan Ogilvie 62609a0088 Improve the default .gitignore that's generated
1 year ago
Duncan Ogilvie e0d8a085db
Merge pull request #121 from anthonyprintup/relative-paths
1 year ago
Duncan Ogilvie 56da4144ae
Merge pull request #125 from anthonyprintup/fix-124
1 year ago
Duncan Ogilvie 9388b3f29d
Merge pull request #123 from anthonyprintup/quotes-fix
1 year ago
Anthony Printup 079644cfeb
fix(generator): Prevent calling `std::string::back` on an empty string
1 year ago
Anthony Printup a7ca9f04c0
fix(relative-paths): Replaced `has_parent_path` with a simpler path check
1 year ago
Anthony Printup 339c2aac94
fix(generator): Prevent command arguments from being quoted when generating tests
1 year ago
Anthony Printup c573c9a76d
test(relative-paths): Add the test-library file to libs
1 year ago
Anthony Printup b24a0a2fdc
test(relative-paths): Make `link-libraries` conditional to the Windows platform
1 year ago
Anthony Printup 854e8817c4
test(relative-paths): Remove inlined CMake from cmake.toml
1 year ago
Anthony Printup 1c2947b2fc
fix(relative-paths): Prevent processing paths which may contain CMake macros, added a check to see if the library that's being linked exists on disk
1 year ago
Anthony Printup 19e61aa1aa
test(relative-paths): Attempt to fix the tests workflow
1 year ago
Anthony Printup bfa1e1fe0d
test(relative-paths): Added the latest `relative-paths` test to the tests workflow
1 year ago
Anthony Printup 56bd78f7ad
feat(project-parser): Added support for relative paths in `link-libraries`
1 year ago
Anthony Printup 4362b3547f
test(relative-paths): Added tests for #116
1 year ago
Duncan Ogilvie 08204d9847 Bump to 0.2.25
1 year ago
Duncan Ogilvie 2202aa8173
Merge pull request #120 from anthonyprintup/new-lines-fix
1 year ago
Anthony Printup d3829fb6cd Trim additional new lines at the end of the generated CMakeLists.txt
1 year ago
Duncan Ogilvie 3fb095210a
Merge pull request #118 from ZehMatt/vcpkg-errors
1 year ago
ζeh Matt 9f99365a25
Report better error messages for vcpkg projects and features
1 year ago
Duncan Ogilvie be387141b5 Add a 'Compiler flags' example to the documentation
1 year ago
Duncan Ogilvie 09ded38f70
Merge pull request #115 from build-cpp/clang-cl-condition
1 year ago
Duncan Ogilvie ad90b1ad3e Add 'clang-cl' and 'clang-any' conditions
1 year ago
Duncan Ogilvie 5bfa03d311 Bump to 0.2.24
1 year ago
Duncan Ogilvie bdd9d393d6
Merge pull request #113 from build-cpp/editorconfig
1 year ago
Duncan Ogilvie 1deaec2b41 Add .editorconfig and linting
1 year ago
Duncan Ogilvie cfee3bbc14
Merge pull request #111 from anthonyprintup/fetch-content-system
1 year ago
Anthony Printup bcaa60cabe
feat: Added SYSTEM flag support to fetch-content
1 year ago
Duncan Ogilvie 7b1d33395e Bump to 0.2.23
2 years ago
Duncan Ogilvie 240fafc102
Merge pull request #101 from build-cpp/minor-cleanup
2 years ago
Duncan Ogilvie 7a7ddf2699 Speed up the pipeline by using Ninja
2 years ago
Duncan Ogilvie 605a04e72d Fix formatting of files
2 years ago
Duncan Ogilvie fd7f078127 Add workflow for checking clang-format
2 years ago
Duncan Ogilvie ee0e0d71d9 Remove a bunch of dead code
2 years ago
Duncan Ogilvie 9d6897b572 Suppress warnings related to DOWNLOAD_EXTRACT_TIMESTAMP
2 years ago
Duncan Ogilvie f8c34e5545 Error when [vcpkg] is used from a non-root project
2 years ago
Duncan Ogilvie e6d783ea20 Fix a few clang-tidy warnings
2 years ago
Duncan Ogilvie ee7fe38a7b
Merge pull request #100 from build-cpp/include-subdir-bugfix
2 years ago
Duncan Ogilvie 66b2aad843 Fix a bug where includes in a subdir would not work
2 years ago
Duncan Ogilvie d2e376080a Bump to 0.2.22
2 years ago
Duncan Ogilvie 634908c651
Merge pull request #97 from build-cpp/cmkr-init-regression
2 years ago
Duncan Ogilvie 33911f3f1b Fix a regression where cmkr init wouldn't create any source files
2 years ago
Duncan Ogilvie e650073bef Bump to 0.2.21
2 years ago
Duncan Ogilvie 96e44f7771
Merge pull request #96 from build-cpp/check-sources
2 years ago
Duncan Ogilvie cb9ad5cdb3 Improve error messages and check if specified sources exist
2 years ago
Duncan Ogilvie c785b540d3 Document target templates
2 years ago
Duncan Ogilvie 275b73eefa Remove debug print
2 years ago
Duncan Ogilvie ea49a3acb7 Bump to 0.2.20
2 years ago
Duncan Ogilvie 257e14c869
Merge pull request #93 from build-cpp/gitattributes
2 years ago
Duncan Ogilvie 7508816fe7
Merge pull request #94 from build-cpp/user-experience
2 years ago
Duncan Ogilvie 45678bc5bc Automatically generate .gitattributes and .gitignore with cmkr init
2 years ago
Duncan Ogilvie b869e5646f Add a special "root" value for [options]
2 years ago
Duncan Ogilvie a499df84b1 Bump to 0.2.19
2 years ago
Duncan Ogilvie 49440f99e0
Merge pull request #92 from build-cpp/improved-options
2 years ago
Duncan Ogilvie 0c19c3da0c Improve the code related to [options]
2 years ago
Duncan Ogilvie 233cadadd0
Merge pull request #90 from mike1k/sanity
2 years ago
Duncan Ogilvie 58a5a935e5 Add a bunch of missing languages and extensions and refactor
2 years ago
Duncan Ogilvie e69427bf94
Merge pull request #91 from build-cpp/clang-condition
2 years ago
Duncan Ogilvie bc6359805d Change the `clang` condition to not detect clang-cl
2 years ago
Duncan Ogilvie 8dcc11e349 Add helper function Project::cmake_minimum_version for version-dependent features
2 years ago
Duncan Ogilvie 51dc49e6f1 Improve CI performance for non-tagged builds
2 years ago
mike 7d27c28b59 Fixed failing tests due to implicit project languages (internal list empty)
2 years ago
Duncan Ogilvie ae6bea3b58 Improve reference documentation
2 years ago
Duncan Ogilvie 5a0eda7abc Automatically generate release notes
2 years ago

@ -19,7 +19,7 @@ AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: true AlwaysBreakTemplateDeclarations: true
BinPackArguments: true BinPackArguments: true
BinPackParameters: true BinPackParameters: true
BraceWrapping: BraceWrapping:
AfterClass: false AfterClass: false
AfterControlStatement: false AfterControlStatement: false
AfterEnum: false AfterEnum: false
@ -72,7 +72,7 @@ PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 60 PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Right PointerAlignment: Right
ReflowComments: true ReflowComments: true
SortIncludes: true SortIncludes: false
SortUsingDeclarations: true SortUsingDeclarations: true
SpaceAfterCStyleCast: false SpaceAfterCStyleCast: false
SpaceAfterTemplateKeyword: true SpaceAfterTemplateKeyword: true

@ -0,0 +1,24 @@
root = true
[*]
end_of_line = lf
trim_trailing_whitespace = true
charset = utf-8
[*.{c,h,cpp,hpp,toml}]
indent_style = space
insert_final_newline = true
[{CMakeLists.txt, *.cmake}]
indent_style = tab
tab_width = 8
insert_final_newline = true
# Exclude the third_party folder
[/third_party/**]
charset = unset
end_of_line = unset
insert_final_newline = unset
trim_trailing_whitespace = unset
indent_style = unset
indent_size = unset

@ -1,21 +1,28 @@
name: CMake name: build
on: [push, pull_request] on: [push, pull_request]
jobs: jobs:
build: cmake:
# Skip building pull requests from the same repository # Skip building pull requests from the same repository
if: ${{ github.event_name == 'push' || (github.event_name == 'pull_request' && 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 }} runs-on: ${{ matrix.os }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [windows-2019, macos-10.15, ubuntu-20.04] os: [windows-2022, macos-11, ubuntu-20.04]
env: env:
BUILD_TYPE: Release BUILD_TYPE: 'Release'
CMAKE_GENERATOR: 'Ninja'
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Install Ninja
uses: seanmiddleditch/gha-setup-ninja@6263846cf3c17009dfc81604efabae16044fc074 # master
- name: Visual Studio Development Environment
uses: ilammy/msvc-dev-cmd@cec98b9d092141f74527d0afa6feb2af698cfe89 # v1.12.1
- name: Tag cmkr.cmake - name: Tag cmkr.cmake
if: ${{ startsWith(github.ref, 'refs/tags/') }} if: ${{ startsWith(github.ref, 'refs/tags/') }}
@ -38,28 +45,31 @@ jobs:
ctest -C ${{ env.BUILD_TYPE }} --verbose ctest -C ${{ env.BUILD_TYPE }} --verbose
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v3
with: with:
name: ${{ github.event.repository.name }}-${{ matrix.os }} name: ${{ github.event.repository.name }}-${{ matrix.os }}
path: install/bin/* path: install/bin/*
- name: Get lowercase OS name - name: Get lowercase OS name
id: osname id: osname
uses: ASzc/change-string-case-action@v1 uses: ASzc/change-string-case-action@07c1e24a97f0951e13f88870b99c058fcf0b14cf # v5
if: ${{ startsWith(github.ref, 'refs/tags/') }}
with: with:
string: ${{ runner.os }} string: ${{ runner.os }}
- name: Compress artifacts - name: Compress artifacts
uses: vimtor/action-zip@v1 uses: vimtor/action-zip@26a249fb00d43ca98dad77a4b3838025fc226aa1 # v1.1
if: ${{ startsWith(github.ref, 'refs/tags/') }}
with: with:
files: install/bin/ files: install/bin/
dest: ${{ github.event.repository.name }}-${{ steps.osname.outputs.lowercase }}.zip dest: ${{ github.event.repository.name }}-${{ steps.osname.outputs.lowercase }}.zip
- name: Release - name: Release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v0.1.15
if: ${{ startsWith(github.ref, 'refs/tags/') }} if: ${{ startsWith(github.ref, 'refs/tags/') }}
with: with:
prerelease: ${{ !startsWith(github.ref, 'refs/tags/v') || contains(github.ref, '-pre') }} prerelease: ${{ !startsWith(github.ref, 'refs/tags/v') || contains(github.ref, '-pre') }}
files: ${{ github.event.repository.name }}-${{ steps.osname.outputs.lowercase }}.zip files: ${{ github.event.repository.name }}-${{ steps.osname.outputs.lowercase }}.zip
generate_release_notes: true
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

@ -0,0 +1,37 @@
name: lint
on: [push]
jobs:
clang-format:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: clang-format
id: clang-format
uses: DoozyX/clang-format-lint-action@c3b2c943e924028b93a707a5b1b017976ab8d50c # v0.15
with:
exclude: './third_party'
extensions: 'c,h,cpp,hpp'
clangFormatVersion: 15
style: file
- name: clang-format instructions
if: ${{ failure() && steps.clang-format.outcome == 'failure' }}
run: |
# Instructions for fixing the formatting errors
echo -e "\n\033[0;31mTo fix the formatting, run:\nclang-format -style=file -i \$(git ls-files \"*.c\" \"*.h\" \"*.cpp\" \"*.hpp\")\033[0m\n"
exit 1
editorconfig:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Run editorconfig-checker
uses: editorconfig-checker/action-editorconfig-checker@d4fca16fc71adef10fbe101903b654449fa9570c # master 2022-03-15

3
.gitignore vendored

@ -9,4 +9,5 @@ build*/
.idea/ .idea/
cmake-build*/ cmake-build*/
CMakeLists.txt.user CMakeLists.txt.user
.vscode/ .vscode/
.DS_Store

4
CMakeLists.txt generated

@ -22,7 +22,7 @@ project(cmkr
LANGUAGES LANGUAGES
CXX CXX
VERSION VERSION
0.2.18 0.2.25
DESCRIPTION DESCRIPTION
"CMakeLists generator from TOML" "CMakeLists generator from TOML"
) )
@ -61,14 +61,12 @@ set(cmkr_SOURCES
"src/arguments.cpp" "src/arguments.cpp"
"src/build.cpp" "src/build.cpp"
"src/cmake_generator.cpp" "src/cmake_generator.cpp"
"src/error.cpp"
"src/help.cpp" "src/help.cpp"
"src/main.cpp" "src/main.cpp"
"src/project_parser.cpp" "src/project_parser.cpp"
"include/arguments.hpp" "include/arguments.hpp"
"include/build.hpp" "include/build.hpp"
"include/cmake_generator.hpp" "include/cmake_generator.hpp"
"include/error.hpp"
"include/fs.hpp" "include/fs.hpp"
"include/help.hpp" "include/help.hpp"
"include/literals.hpp" "include/literals.hpp"

@ -4,7 +4,7 @@ cmkr-include = false
[project] [project]
name = "cmkr" name = "cmkr"
version = "0.2.18" version = "0.2.25"
description = "CMakeLists generator from TOML" description = "CMakeLists generator from TOML"
languages = ["CXX"] languages = ["CXX"]
include-after = [ include-after = [

@ -1,78 +1,78 @@
cmake_minimum_required(VERSION 3.20) cmake_minimum_required(VERSION 3.20)
if(NOT CMAKE_SCRIPT_MODE_FILE) if(NOT CMAKE_SCRIPT_MODE_FILE)
message(FATAL_ERROR "Usage: cmake -P bump_version.cmake [1.2.3]") message(FATAL_ERROR "Usage: cmake -P bump_version.cmake [1.2.3]")
endif() endif()
if(NOT EXISTS "${CMAKE_SOURCE_DIR}/cmake.toml") if(NOT EXISTS "${CMAKE_SOURCE_DIR}/cmake.toml")
message(FATAL_ERROR "Cannot find cmake.toml") message(FATAL_ERROR "Cannot find cmake.toml")
endif() endif()
if(NOT EXISTS "${CMAKE_SOURCE_DIR}/cmake/cmkr.cmake") if(NOT EXISTS "${CMAKE_SOURCE_DIR}/cmake/cmkr.cmake")
message(FATAL_ERROR "Cannot find cmkr.cmake") message(FATAL_ERROR "Cannot find cmkr.cmake")
endif() endif()
# Validate branch # Validate branch
find_package(Git REQUIRED) find_package(Git REQUIRED)
execute_process(COMMAND "${GIT_EXECUTABLE}" branch --show-current OUTPUT_VARIABLE GIT_BRANCH) execute_process(COMMAND "${GIT_EXECUTABLE}" branch --show-current OUTPUT_VARIABLE GIT_BRANCH)
string(STRIP "${GIT_BRANCH}" GIT_BRANCH) string(STRIP "${GIT_BRANCH}" GIT_BRANCH)
if(NOT GIT_BRANCH STREQUAL "main") if(NOT GIT_BRANCH STREQUAL "main")
message(FATAL_ERROR "You need to be on the main branch, you are on: ${GIT_BRANCH}") message(FATAL_ERROR "You need to be on the main branch, you are on: ${GIT_BRANCH}")
endif() endif()
file(READ "${CMAKE_SOURCE_DIR}/cmake.toml" CMAKE_TOML) file(READ "${CMAKE_SOURCE_DIR}/cmake.toml" CMAKE_TOML)
string(FIND "${CMAKE_TOML}" "[project]" PROJECT_INDEX) string(FIND "${CMAKE_TOML}" "[project]" PROJECT_INDEX)
string(SUBSTRING "${CMAKE_TOML}" ${PROJECT_INDEX} -1 CMAKE_TOML_PROJECT) string(SUBSTRING "${CMAKE_TOML}" ${PROJECT_INDEX} -1 CMAKE_TOML_PROJECT)
set(SEMVER_REGEX "([0-9]+)\\.([0-9]+)\\.([0-9]+)") set(SEMVER_REGEX "([0-9]+)\\.([0-9]+)\\.([0-9]+)")
set(VERSION_REGEX "version = \"${SEMVER_REGEX}\"") set(VERSION_REGEX "version = \"${SEMVER_REGEX}\"")
if(CMAKE_TOML_PROJECT MATCHES "${VERSION_REGEX}") if(CMAKE_TOML_PROJECT MATCHES "${VERSION_REGEX}")
set(MAJOR "${CMAKE_MATCH_1}") set(MAJOR "${CMAKE_MATCH_1}")
set(MINOR "${CMAKE_MATCH_2}") set(MINOR "${CMAKE_MATCH_2}")
set(PATCH "${CMAKE_MATCH_3}") set(PATCH "${CMAKE_MATCH_3}")
set(OLDVERSION "${MAJOR}.${MINOR}.${PATCH}") set(OLDVERSION "${MAJOR}.${MINOR}.${PATCH}")
else() else()
message(FATAL_ERROR "Failed to match semantic version in cmake.toml") message(FATAL_ERROR "Failed to match semantic version in cmake.toml")
endif() endif()
if(CMAKE_ARGV3) if(CMAKE_ARGV3)
if(NOT CMAKE_ARGV3 MATCHES "${SEMVER_REGEX}") if(NOT CMAKE_ARGV3 MATCHES "${SEMVER_REGEX}")
message(FATAL_ERROR "Invalid semantic version number '${CMAKE_ARGV3}'") message(FATAL_ERROR "Invalid semantic version number '${CMAKE_ARGV3}'")
endif() endif()
set(NEWVERSION "${CMAKE_ARGV3}") set(NEWVERSION "${CMAKE_ARGV3}")
else() else()
math(EXPR NEWPATCH "${PATCH} + 1") math(EXPR NEWPATCH "${PATCH} + 1")
set(NEWVERSION "${MAJOR}.${MINOR}.${NEWPATCH}") set(NEWVERSION "${MAJOR}.${MINOR}.${NEWPATCH}")
endif() endif()
message(STATUS "Version ${OLDVERSION} -> ${NEWVERSION}") message(STATUS "Version ${OLDVERSION} -> ${NEWVERSION}")
find_program(CMKR_EXECUTABLE "cmkr" PATHS "${CMAKE_SOURCE_DIR}/build" PATH_SUFFIXES Debug Release RelWithDebInfo MinSizeRel NO_CACHE REQUIRED) find_program(CMKR_EXECUTABLE "cmkr" PATHS "${CMAKE_SOURCE_DIR}/build" PATH_SUFFIXES Debug Release RelWithDebInfo MinSizeRel NO_CACHE REQUIRED)
message(STATUS "Found cmkr: ${CMKR_EXECUTABLE}") message(STATUS "Found cmkr: ${CMKR_EXECUTABLE}")
# Replace version in cmake.toml # Replace version in cmake.toml
string(REPLACE "version = \"${OLDVERSION}\"" "version = \"${NEWVERSION}\"" CMAKE_TOML "${CMAKE_TOML}") string(REPLACE "version = \"${OLDVERSION}\"" "version = \"${NEWVERSION}\"" CMAKE_TOML "${CMAKE_TOML}")
file(CONFIGURE file(CONFIGURE
OUTPUT "${CMAKE_SOURCE_DIR}/cmake.toml" OUTPUT "${CMAKE_SOURCE_DIR}/cmake.toml"
CONTENT "${CMAKE_TOML}" CONTENT "${CMAKE_TOML}"
@ONLY @ONLY
NEWLINE_STYLE LF NEWLINE_STYLE LF
) )
# Run cmkr gen # Run cmkr gen
execute_process(COMMAND "${CMKR_EXECUTABLE}" gen RESULT_VARIABLE CMKR_EXEC_RESULT) execute_process(COMMAND "${CMKR_EXECUTABLE}" gen RESULT_VARIABLE CMKR_EXEC_RESULT)
if(NOT CMKR_EXEC_RESULT EQUAL 0) if(NOT CMKR_EXEC_RESULT EQUAL 0)
message(FATAL_ERROR "cmkr gen failed (exit code ${CMKR_EXEC_RESULT})") message(FATAL_ERROR "cmkr gen failed (exit code ${CMKR_EXEC_RESULT})")
endif() endif()
# Replace version in cmkr.cmake # Replace version in cmkr.cmake
file(READ "${CMAKE_SOURCE_DIR}/cmake/cmkr.cmake" 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}") string(REGEX REPLACE "CMKR_TAG \"[^\"]+\"" "CMKR_TAG \"v${NEWVERSION}\"" CMKR_CMAKE "${CMKR_CMAKE}")
file(CONFIGURE file(CONFIGURE
OUTPUT "${CMAKE_SOURCE_DIR}/cmake/cmkr.cmake" OUTPUT "${CMAKE_SOURCE_DIR}/cmake/cmkr.cmake"
CONTENT "${CMKR_CMAKE}" CONTENT "${CMKR_CMAKE}"
@ONLY @ONLY
NEWLINE_STYLE LF NEWLINE_STYLE LF
) )
# Print git commands # 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}") 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}")

@ -2,7 +2,7 @@ include_guard()
# Change these defaults to point to your infrastructure if desired # Change these defaults to point to your infrastructure if desired
set(CMKR_REPO "https://github.com/build-cpp/cmkr" CACHE STRING "cmkr git repository" FORCE) set(CMKR_REPO "https://github.com/build-cpp/cmkr" CACHE STRING "cmkr git repository" FORCE)
set(CMKR_TAG "v0.2.18" CACHE STRING "cmkr git tag (this needs to be available forever)" FORCE) set(CMKR_TAG "v0.2.25" CACHE STRING "cmkr git tag (this needs to be available forever)" FORCE)
set(CMKR_COMMIT_HASH "" CACHE STRING "cmkr git commit hash (optional)" FORCE) set(CMKR_COMMIT_HASH "" CACHE STRING "cmkr git commit hash (optional)" FORCE)
# To bootstrap/generate a cmkr project: cmake -P cmkr.cmake # To bootstrap/generate a cmkr project: cmake -P cmkr.cmake

@ -15,7 +15,7 @@ function(generate_documentation)
message(FATAL_ERROR "This should not happen (wrong regex?)") message(FATAL_ERROR "This should not happen (wrong regex?)")
endif() endif()
endforeach() endforeach()
# Delete previously generated examples # Delete previously generated examples
set(example_folder "${PROJECT_SOURCE_DIR}/docs/examples") set(example_folder "${PROJECT_SOURCE_DIR}/docs/examples")
file(GLOB example_files "${example_folder}/*.md") file(GLOB example_files "${example_folder}/*.md")
@ -41,21 +41,21 @@ function(generate_documentation)
# Read cmake.toml file # Read cmake.toml file
file(READ "${test_toml}" test_contents NO_HEX_CONVERSION) file(READ "${test_toml}" test_contents NO_HEX_CONVERSION)
string(LENGTH "${test_contents}" toml_length) string(LENGTH "${test_contents}" toml_length)
# Extract header text # Extract header text
string(REGEX MATCH "^(\n*(#[^\n]+\n)+\n*)" EXAMPLE_HEADER "${test_contents}") string(REGEX MATCH "^(\n*(#[^\n]+\n)+\n*)" EXAMPLE_HEADER "${test_contents}")
string(LENGTH "${EXAMPLE_HEADER}" header_length) string(LENGTH "${EXAMPLE_HEADER}" header_length)
string(STRIP "${EXAMPLE_HEADER}" EXAMPLE_HEADER) string(STRIP "${EXAMPLE_HEADER}" EXAMPLE_HEADER)
string(REGEX REPLACE "\n# ?" "\n\n" EXAMPLE_HEADER "\n${EXAMPLE_HEADER}") string(REGEX REPLACE "\n# ?" "\n\n" EXAMPLE_HEADER "\n${EXAMPLE_HEADER}")
string(STRIP "${EXAMPLE_HEADER}" EXAMPLE_HEADER) string(STRIP "${EXAMPLE_HEADER}" EXAMPLE_HEADER)
# Extract footer text # Extract footer text
string(REGEX MATCH "(((#[^\n]+)(\n+|$))+)$" EXAMPLE_FOOTER "${test_contents}") string(REGEX MATCH "(((#[^\n]+)(\n+|$))+)$" EXAMPLE_FOOTER "${test_contents}")
string(LENGTH "${EXAMPLE_FOOTER}" footer_length) string(LENGTH "${EXAMPLE_FOOTER}" footer_length)
string(STRIP "${EXAMPLE_FOOTER}" EXAMPLE_FOOTER) string(STRIP "${EXAMPLE_FOOTER}" EXAMPLE_FOOTER)
string(REGEX REPLACE "\n# ?" "\n\n" EXAMPLE_FOOTER "\n${EXAMPLE_FOOTER}") string(REGEX REPLACE "\n# ?" "\n\n" EXAMPLE_FOOTER "\n${EXAMPLE_FOOTER}")
string(STRIP "${EXAMPLE_FOOTER}" EXAMPLE_FOOTER) string(STRIP "${EXAMPLE_FOOTER}" EXAMPLE_FOOTER)
# Extract toml body # Extract toml body
math(EXPR toml_length "${toml_length}-${header_length}-${footer_length}") math(EXPR toml_length "${toml_length}-${header_length}-${footer_length}")
string(SUBSTRING "${test_contents}" ${header_length} ${toml_length} EXAMPLE_TOML) string(SUBSTRING "${test_contents}" ${header_length} ${toml_length} EXAMPLE_TOML)
@ -64,7 +64,7 @@ function(generate_documentation)
# Extract title from description # Extract title from description
if("${EXAMPLE_TOML}" MATCHES "description *= *\"([^\"]+)\"") if("${EXAMPLE_TOML}" MATCHES "description *= *\"([^\"]+)\"")
set(EXAMPLE_TITLE "${CMAKE_MATCH_1}") set(EXAMPLE_TITLE "${CMAKE_MATCH_1}")
# Generate documentation markdown page # Generate documentation markdown page
configure_file("${PROJECT_SOURCE_DIR}/cmake/example.md.in" "${example_folder}/${EXAMPLE_PERMALINK}.md" @ONLY NEWLINE_STYLE LF) configure_file("${PROJECT_SOURCE_DIR}/cmake/example.md.in" "${example_folder}/${EXAMPLE_PERMALINK}.md" @ONLY NEWLINE_STYLE LF)
else() else()

@ -1,7 +1,7 @@
namespace cmkr { namespace cmkr {
namespace resources { namespace resources {
static const char* @RESOURCE_NAME@ = R"RESOURCE(@RESOURCE_CONTENTS@)RESOURCE"; static const char* @RESOURCE_NAME@ = R"RESOURCE(@RESOURCE_CONTENTS@)RESOURCE";
} }
} }

@ -1 +1 @@
cmkr.build cmkr.build

@ -39,6 +39,32 @@ include-before = ["cmake/before-project.cmake"]
include-after = ["cmake/after-project.cmake"] include-after = ["cmake/after-project.cmake"]
``` ```
### Languages
Supported languages are (see [`enable_language`](https://cmake.org/cmake/help/latest/command/enable_language.html) for more information):
- `C`
- `CXX` → C++
- `CSharp` → C#
- `CUDA`
- `OBJC` → Objective-C
- `OBJCXX` → Objective-C++
- `Fortran`
- `HIP`
- `ISPC`
- `Swift`
- `ASM`
- `ASM_MASM` → [Microsoft Macro Assembler (MASM)](https://learn.microsoft.com/en-US/cpp/assembler/masm/masm-for-x64-ml64-exe)
- `ASM_NASM` → [Netwide Assembler (NASM)](https://www.nasm.us)
- `ASM_MARMASM` [Microsoft ARM Assembler](https://learn.microsoft.com/en-us/cpp/assembler/arm/arm-assembler-command-line-reference)
- `ASM-ATT`
- `Java` (undocumented)
- `RC` (undocumented)
After a language is enabled, adding sources files with the corresponding extension to your target will automatically use the appropriate compiler/assembler for it.
_Note_: It is generally discouraged to disable the `C` language, unless you are absolutely sure it is not used. Sometimes projects added with `fetch-content` implicitly require it and the error messages can be extremely confusing.
## Conditions ## Conditions
You can specify your own conditions and use them in any `condition` field: You can specify your own conditions and use them in any `condition` field:
@ -49,7 +75,7 @@ arch64 = "CMAKE_SIZEOF_VOID_P EQUAL 8"
arch32 = "CMAKE_SIZEOF_VOID_P EQUAL 4" arch32 = "CMAKE_SIZEOF_VOID_P EQUAL 4"
``` ```
This will make the `arch64` and `arch32` conditions available with their respective CMake expressions. This will make the `arch64` and `arch32` conditions available with their respective CMake expressions.
You can also prefix most keys with `condition.` to represent a conditional: You can also prefix most keys with `condition.` to represent a conditional:
@ -72,8 +98,10 @@ unix = "UNIX"
bsd = "CMAKE_SYSTEM_NAME MATCHES \"BSD\"" bsd = "CMAKE_SYSTEM_NAME MATCHES \"BSD\""
linux = "CMAKE_SYSTEM_NAME MATCHES \"Linux\"" linux = "CMAKE_SYSTEM_NAME MATCHES \"Linux\""
gcc = "CMAKE_CXX_COMPILER_ID STREQUAL \"GNU\" OR CMAKE_C_COMPILER_ID STREQUAL \"GNU\"" gcc = "CMAKE_CXX_COMPILER_ID STREQUAL \"GNU\" OR CMAKE_C_COMPILER_ID STREQUAL \"GNU\""
clang = "CMAKE_CXX_COMPILER_ID MATCHES \"Clang\" OR CMAKE_C_COMPILER_ID MATCHES \"Clang\""
msvc = "MSVC" msvc = "MSVC"
clang = "(CMAKE_CXX_COMPILER_ID MATCHES \"Clang\" AND NOT CMAKE_CXX_COMPILER_FRONTEND_VARIANT MATCHES \"^MSVC$\") OR (CMAKE_C_COMPILER_ID MATCHES \"Clang\" AND NOT CMAKE_C_COMPILER_FRONTEND_VARIANT MATCHES \"^MSVC$\")"
clang-cl = "(CMAKE_CXX_COMPILER_ID MATCHES \"Clang\" AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT MATCHES \"^MSVC$\") OR (CMAKE_C_COMPILER_ID MATCHES \"Clang\" AND CMAKE_C_COMPILER_FRONTEND_VARIANT MATCHES \"^MSVC$\")"
clang-any = "CMAKE_CXX_COMPILER_ID MATCHES \"Clang\" OR CMAKE_C_COMPILER_ID MATCHES \"Clang\""
root = "CMKR_ROOT_PROJECT" root = "CMKR_ROOT_PROJECT"
x64 = "CMAKE_SIZEOF_VOID_P EQUAL 8" x64 = "CMAKE_SIZEOF_VOID_P EQUAL 8"
x32 = "CMAKE_SIZEOF_VOID_P EQUAL 4" x32 = "CMAKE_SIZEOF_VOID_P EQUAL 4"
@ -94,6 +122,19 @@ include-before = ["cmake/before-subdir.cmake"]
include-after = ["cmake/after-subdir.cmake"] include-after = ["cmake/after-subdir.cmake"]
``` ```
## Options
```toml
[options]
MYPROJECT_BUILD_TESTS = false
MYPROJECT_SPECIAL_OPTION = { value = true, help = "Docstring for this option." }
MYPROJECT_BUILD_EXAMPLES = "root"
```
Options correspond to [CMake cache variables](https://cmake.org/cmake/help/book/mastering-cmake/chapter/CMake%20Cache.html) that can be used to customize your project at configure-time. You can configure with `cmake -DMYPROJECT_BUILD_TESTS=ON` to enable the option. Every option automatically gets a corresponding [condition](#conditions).
The special value `root` can be used to set the option to `true` if the project is compiled as the root project (it will be `false` if someone is including your project via `[fetch-content]` or `[subdir]`).
## Variables ## Variables
```toml ```toml
@ -137,6 +178,8 @@ components = ["mycomponent"]
condition = "mycondition" condition = "mycondition"
git = "https://github.com/myuser/gitcontent" git = "https://github.com/myuser/gitcontent"
tag = "v0.1" tag = "v0.1"
shallow = false
system = false
[fetch-content.svncontent] [fetch-content.svncontent]
condition = "mycondition" condition = "mycondition"
@ -146,7 +189,10 @@ rev = "svn_rev"
[fetch-content.urlcontent] [fetch-content.urlcontent]
condition = "mycondition" condition = "mycondition"
url = "https://content-host.com/urlcontent.zip" url = "https://content-host.com/urlcontent.zip"
hash = "123123123123" # These are equivalent, supported algorithms:
# md5, sha1, sha224, sha256, sha384, sha512, sha3_224, sha3_256, sha3_384, sha3_512
hash = "SHA1 502a4e25b8b209889c99c7fa0732102682c2e4ff"
sha1 = "502a4e25b8b209889c99c7fa0732102682c2e4ff"
``` ```
## Targets ## Targets
@ -195,6 +241,29 @@ CXX_STANDARD_REQUIRED = true
FOLDER = "MyFolder" FOLDER = "MyFolder"
``` ```
## Templates
To avoid repeating yourself you can create your own target type and use it in your targets:
```toml
[template.example]
condition = "MYPROJECT_BUILD_EXAMPLES"
type = "executable"
link-libraries = ["myproject::mylib"]
add-function = ""
pass-sources = false
# Properties from the template are merged with the ones here
[target.myexample]
type = "example"
sources = ["src/myexample.cpp"]
```
The properties declared on a `template` are the same as the ones you use for targets. The only exceptions are:
- `add-function`: Specifies a custom add function. Projects like [pybind11](https://pybind11.readthedocs.io/en/stable/cmake/index.html#new-findpython-mode) have their own `add_xxx` function, which you can specify here.
- `pass-sources`: Pass sources directly to the add function instead of using `target_sources`.
## Tests and installation (unfinished) ## Tests and installation (unfinished)
**Note**: The `[[test]]` and `[[install]]` are unfinished features and will likely change in a future release. **Note**: The `[[test]]` and `[[install]]` are unfinished features and will likely change in a future release.

@ -0,0 +1,31 @@
---
# Automatically generated from tests/compile-options/cmake.toml - DO NOT EDIT
layout: default
title: Compiler flags
permalink: /examples/compile-options
parent: Examples
nav_order: 9
---
# Compiler flags
Example project that sets compiler/linker flags for various platforms.
```toml
[project]
name = "compile-options"
description = "Compiler flags"
[target.hello]
type = "executable"
sources = ["src/main.cpp"]
msvc.compile-options = ["/W2"]
gcc.compile-options = ["-Wall"]
clang.compile-options = ["-Wall"]
```
The `hello` target uses [conditions](/cmake-toml#conditions) to set different compiler flags depending on the platform. See the [targets](/cmake-toml/#targets) documentation for other things you can set.
_Note_: In general you only want to specify flags _required_ to compile your code without errors.
<sup><sub>This page was automatically generated from [tests/compile-options/cmake.toml](https://github.com/build-cpp/cmkr/tree/main/tests/compile-options/cmake.toml).</sub></sup>

@ -1,10 +1,10 @@
--- ---
layout: null layout: null
permalink: /getting-started/ permalink: /getting-started/
--- ---
<html> <html>
<head> <head>
<meta http-equiv="refresh" content="0;url=https://cmkr.build"> <meta http-equiv="refresh" content="0;url=https://cmkr.build">
</head> </head>
</html> </html>

@ -11,9 +11,3 @@ int install();
} // namespace build } // namespace build
} // namespace cmkr } // namespace cmkr
int cmkr_build_run(int argc, char **argv);
int cmkr_build_clean();
int cmkr_build_install();

@ -1,27 +0,0 @@
#pragma once
namespace cmkr {
namespace error {
struct Status {
enum class Code {
Success = 0,
RuntimeError,
InitError,
GenerationError,
BuildError,
CleanError,
InstallError,
};
Status(Code ec) noexcept;
operator int() const noexcept;
Code code() const noexcept;
private:
Code ec_ = Code::Success;
};
} // namespace error
} // namespace cmkr
const char *cmkr_error_status_string(int);

@ -1,8 +1,6 @@
#pragma once #pragma once
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || \ #if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include)
(defined(__cplusplus) && __cplusplus >= 201703L)) && \
defined(__has_include)
#if __has_include(<filesystem>) && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) #if __has_include(<filesystem>) && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500)
#define GHC_USE_STD_FS #define GHC_USE_STD_FS
#include <filesystem> #include <filesystem>
@ -12,4 +10,4 @@ namespace fs = std::filesystem;
#ifndef GHC_USE_STD_FS #ifndef GHC_USE_STD_FS
#include <ghc/filesystem.hpp> #include <ghc/filesystem.hpp>
namespace fs = ghc::filesystem; namespace fs = ghc::filesystem;
#endif #endif

@ -9,7 +9,3 @@ const char *message() noexcept;
} // namespace help } // namespace help
} // namespace cmkr } // namespace cmkr
const char *cmkr_help_version(void);
const char *cmkr_help_message(void);

@ -1,10 +1,11 @@
#pragma once #pragma once
#include <mpark/variant.hpp> #include <vector>
#include <string> #include <string>
#include <mpark/variant.hpp>
#include <tsl/ordered_map.h> #include <tsl/ordered_map.h>
#include <tsl/ordered_set.h> #include <tsl/ordered_set.h>
#include <vector>
namespace cmkr { namespace cmkr {
namespace parser { namespace parser {
@ -16,16 +17,16 @@ using ConditionVector = Condition<std::vector<std::string>>;
struct Variable { struct Variable {
std::string name; std::string name;
std::string comment; std::string help;
mpark::variant<bool, std::string> val; mpark::variant<bool, std::string> value;
bool cache = false; bool cache = false;
bool force = false; bool force = false;
}; };
struct Option { struct Option {
std::string name; std::string name;
std::string comment; std::string help;
bool val = false; mpark::variant<bool, std::string> value;
}; };
struct Package { struct Package {
@ -47,6 +48,10 @@ struct Vcpkg {
}; };
std::vector<Package> packages; std::vector<Package> packages;
bool enabled() const {
return !packages.empty();
}
}; };
enum TargetType { enum TargetType {
@ -150,6 +155,7 @@ struct Content {
Condition<std::string> cmake_after; Condition<std::string> cmake_after;
ConditionVector include_before; ConditionVector include_before;
ConditionVector include_after; ConditionVector include_after;
bool system;
}; };
enum MsvcRuntimeType { enum MsvcRuntimeType {
@ -180,6 +186,7 @@ struct Project {
std::string project_version; std::string project_version;
std::string project_description; std::string project_description;
std::vector<std::string> project_languages; std::vector<std::string> project_languages;
bool project_allow_unknown_languages = false;
MsvcRuntimeType project_msvc_runtime = msvc_last; MsvcRuntimeType project_msvc_runtime = msvc_last;
Condition<std::string> cmake_before; Condition<std::string> cmake_before;
Condition<std::string> cmake_after; Condition<std::string> cmake_after;
@ -199,9 +206,10 @@ struct Project {
Project(const Project *parent, const std::string &path, bool build); Project(const Project *parent, const std::string &path, bool build);
const Project *root() const; const Project *root() const;
bool cmake_minimum_version(int major, int minor) const;
}; };
bool is_root_path(const std::string &path); bool is_root_path(const std::string &path);
} // namespace parser } // namespace parser
} // namespace cmkr } // namespace cmkr

@ -2,10 +2,8 @@
#include "build.hpp" #include "build.hpp"
#include "cmake_generator.hpp" #include "cmake_generator.hpp"
#include "help.hpp" #include "help.hpp"
#include "fs.hpp" #include "fs.hpp"
#include <exception>
#include <iostream>
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
#include <vector> #include <vector>

@ -1,14 +1,10 @@
#include "build.hpp" #include "build.hpp"
#include "cmake_generator.hpp" #include "cmake_generator.hpp"
#include "error.hpp"
#include "project_parser.hpp" #include "project_parser.hpp"
#include "fs.hpp" #include "fs.hpp"
#include <cstddef>
#include <cstdlib> #include <cstdlib>
#include <sstream> #include <sstream>
#include <stdexcept>
#include <system_error>
namespace cmkr { namespace cmkr {
namespace build { namespace build {
@ -17,7 +13,7 @@ int run(int argc, char **argv) {
parser::Project project(nullptr, ".", true); parser::Project project(nullptr, ".", true);
if (argc > 2) { if (argc > 2) {
for (int i = 2; i < argc; ++i) { for (int i = 2; i < argc; ++i) {
project.build_args.push_back(argv[i]); project.build_args.emplace_back(argv[i]);
} }
} }
std::stringstream ss; std::stringstream ss;
@ -48,13 +44,13 @@ int run(int argc, char **argv) {
} }
int clean() { int clean() {
bool ret = false; bool success = false;
parser::Project project(nullptr, ".", true); parser::Project project(nullptr, ".", true);
if (fs::exists(project.build_dir)) { if (fs::exists(project.build_dir)) {
ret = fs::remove_all(project.build_dir); success = fs::remove_all(project.build_dir);
fs::create_directory(project.build_dir); fs::create_directory(project.build_dir);
} }
return !ret; return success ? EXIT_SUCCESS : EXIT_FAILURE;
} }
int install() { int install() {
@ -64,33 +60,3 @@ int install() {
} }
} // namespace build } // namespace build
} // namespace cmkr } // namespace cmkr
int cmkr_build_run(int argc, char **argv) {
try {
return cmkr::build::run(argc, argv);
} catch (const std::system_error &e) {
return e.code().value();
} catch (...) {
return cmkr::error::Status(cmkr::error::Status::Code::BuildError);
}
}
int cmkr_build_clean(void) {
try {
return cmkr::build::clean();
} catch (const std::system_error &e) {
return e.code().value();
} catch (...) {
return cmkr::error::Status(cmkr::error::Status::Code::CleanError);
}
}
int cmkr_build_install(void) {
try {
return cmkr::build::install();
} catch (const std::system_error &e) {
return e.code().value();
} catch (...) {
return cmkr::error::Status(cmkr::error::Status::Code::InstallError);
}
}

@ -1,20 +1,48 @@
#include "cmake_generator.hpp" #include "cmake_generator.hpp"
#include "error.hpp"
#include "literals.hpp" #include "literals.hpp"
#include <resources/cmkr.hpp> #include <resources/cmkr.hpp>
#include "fs.hpp" #include "fs.hpp"
#include "project_parser.hpp" #include "project_parser.hpp"
#include <cstdio> #include <cstdio>
#include <fstream>
#include <memory> #include <memory>
#include <sstream> #include <sstream>
#include <stdexcept> #include <stdexcept>
#include <fstream>
namespace cmkr { namespace cmkr {
namespace gen { namespace gen {
static std::string format(const char *format, tsl::ordered_map<std::string, std::string> variables) { /*
Location: CMake/share/cmake-3.26/Modules
rg "set\(CMAKE_(.+)_SOURCE_FILE_EXTENSIONS"
Links:
- https://gitlab.kitware.com/cmake/cmake/-/issues/24340
- https://cmake.org/cmake/help/latest/command/enable_language.html
*/
static tsl::ordered_map<std::string, std::vector<std::string>> known_languages = {
{"ASM", {".s", ".S", ".asm", ".abs", ".msa", ".s90", ".s43", ".s85", ".s51"}},
{"ASM-ATT", {".s", ".asm"}},
{"ASM_MARMASM", {".asm"}},
{"ASM_MASM", {".asm"}},
{"ASM_NASM", {".nasm", ".asm"}},
{"C", {".c", ".m"}},
{"CSharp", {".cs"}},
{"CUDA", {".cu"}},
{"CXX", {".C", ".M", ".c++", ".cc", ".cpp", ".cxx", ".m", ".mm", ".mpp", ".CPP", ".ixx", ".cppm"}},
{"Fortran", {".f", ".F", ".fpp", ".FPP", ".f77", ".F77", ".f90", ".F90", ".for", ".For", ".FOR", ".f95", ".F95", ".cuf", ".CUF"}},
{"HIP", {".hip"}},
{"ISPC", {".ispc"}},
{"Java", {".java"}},
{"OBJC", {".m"}},
{"OBJCXX", {".M", ".m", ".mm"}},
{"RC", {".rc", ".RC"}},
{"Swift", {".swift"}},
};
static std::string format(const char *format, const tsl::ordered_map<std::string, std::string> &variables) {
std::string s = format; std::string s = format;
for (const auto &itr : variables) { for (const auto &itr : variables) {
size_t start_pos = 0; size_t start_pos = 0;
@ -90,6 +118,19 @@ static void create_file(const fs::path &path, const std::string &contents) {
ofs << contents; ofs << contents;
} }
static std::string read_file(const fs::path &path) {
std::ifstream ifs(path, std::ios::binary);
if (!ifs) {
throw std::runtime_error("Failed to read " + path.string());
}
std::string contents;
ifs.seekg(0, std::ios::end);
contents.resize(ifs.tellg());
ifs.seekg(0, std::ios::beg);
ifs.read(&contents[0], contents.size());
return contents;
}
// CMake target name rules: https://cmake.org/cmake/help/latest/policy/CMP0037.html [A-Za-z0-9_.+\-] // CMake target name rules: https://cmake.org/cmake/help/latest/policy/CMP0037.html [A-Za-z0-9_.+\-]
// TOML bare keys: non-empty strings composed only of [A-Za-z0-9_-] // TOML bare keys: non-empty strings composed only of [A-Za-z0-9_-]
// We replace all non-TOML bare key characters with _ // We replace all non-TOML bare key characters with _
@ -106,18 +147,82 @@ static std::string escape_project_name(const std::string &name) {
return escaped; return escaped;
} }
static void generate_gitfile(const char *gitfile, const std::vector<std::string> &desired_lines) {
// Reference: https://github.com/github/linguist/blob/master/docs/overrides.md#summary
auto lines = desired_lines;
auto generate = [&lines](const char *newline) {
std::string generated;
generated += "# cmkr";
generated += newline;
for (const auto &line : lines) {
generated += line;
generated += newline;
}
return generated;
};
if (!fs::exists(gitfile)) {
create_file(gitfile, generate("\n"));
} else {
auto contents = read_file(gitfile);
std::string line;
auto cr = 0, lf = 0;
auto flush_line = [&line, &lines]() {
auto itr = std::find(lines.begin(), lines.end(), line);
if (itr != lines.end()) {
lines.erase(itr);
}
line.clear();
};
for (size_t i = 0; i < contents.length(); i++) {
if (contents[i] == '\r') {
cr++;
continue;
}
if (contents[i] == '\n') {
lf++;
flush_line();
} else {
line += contents[i];
}
}
if (!line.empty()) {
flush_line();
}
if (!lines.empty()) {
// Append the cmkr .gitattributes using the detected newline
auto newline = cr == lf ? "\r\n" : "\n";
if (!contents.empty() && contents.back() != '\n') {
contents += newline;
}
contents += newline;
contents += generate(newline);
create_file(gitfile, contents);
}
}
}
void generate_project(const std::string &type) { void generate_project(const std::string &type) {
const auto name = escape_project_name(fs::current_path().stem().string()); const auto name = escape_project_name(fs::current_path().stem().string());
if (fs::exists(fs::current_path() / "cmake.toml")) { if (fs::exists(fs::current_path() / "cmake.toml")) {
throw std::runtime_error("Cannot initialize a project when cmake.toml already exists!"); throw std::runtime_error("Cannot initialize a project when cmake.toml already exists!");
} }
// Check if the folder is empty before creating any files
auto is_empty = fs::is_empty(fs::current_path());
// Automatically generate .gitattributes to not skew the statistics of the repo
generate_gitfile(".gitattributes", {"/**/CMakeLists.txt linguist-generated", "/**/cmkr.cmake linguist-vendored"});
// Generate .gitignore with reasonable defaults for CMake
generate_gitfile(".gitignore", {"build*/", "cmake-build*/", "CMakerLists.txt", "CMakeLists.txt.user"});
tsl::ordered_map<std::string, std::string> variables = { tsl::ordered_map<std::string, std::string> variables = {
{"@name", name}, {"@name", name},
{"@type", type}, {"@type", type},
}; };
if (!fs::is_empty(fs::current_path())) { if (!is_empty) {
// Make a backup of an existing CMakeLists.txt if it exists // Make a backup of an existing CMakeLists.txt if it exists
std::error_code ec; std::error_code ec;
fs::rename("CMakeLists.txt", "CMakeLists.txt.bak", ec); fs::rename("CMakeLists.txt", "CMakeLists.txt.bak", ec);
@ -143,7 +248,7 @@ void generate_project(const std::string &type) {
struct CommandEndl { struct CommandEndl {
std::stringstream &ss; std::stringstream &ss;
CommandEndl(std::stringstream &ss) : ss(ss) { explicit CommandEndl(std::stringstream &ss) : ss(ss) {
} }
void endl() { void endl() {
ss << '\n'; ss << '\n';
@ -152,7 +257,7 @@ struct CommandEndl {
struct RawArg { struct RawArg {
RawArg() = default; RawArg() = default;
RawArg(std::string arg) : arg(std::move(arg)) { explicit RawArg(std::string arg) : arg(std::move(arg)) {
} }
std::string arg; std::string arg;
@ -187,7 +292,7 @@ struct Command {
// https://cmake.org/cmake/help/latest/manual/cmake-language.7.html#unquoted-argument // https://cmake.org/cmake/help/latest/manual/cmake-language.7.html#unquoted-argument
// NOTE: Normally '/' does not require quoting according to the documentation but this has been the case here // NOTE: Normally '/' does not require quoting according to the documentation but this has been the case here
// previously, so for backwards compatibility its still here. // previously, so for backwards compatibility its still here.
if (str.find_first_of("()#\"\\'> |/;") == str.npos) if (str.find_first_of("()#\"\\'> |/;") == std::string::npos)
return str; return str;
std::string result; std::string result;
result += "\""; result += "\"";
@ -336,14 +441,15 @@ static std::string tolf(const std::string &str) {
} }
} }
return result; return result;
}; }
struct Generator { struct Generator {
Generator(const parser::Project &project) : project(project) { Generator(const parser::Project &project, fs::path path) : project(project), path(std::move(path)) {
} }
Generator(const Generator &) = delete; Generator(const Generator &) = delete;
const parser::Project &project; const parser::Project &project;
fs::path path;
std::stringstream ss; std::stringstream ss;
int indent = 0; int indent = 0;
@ -373,8 +479,8 @@ struct Generator {
void inject_includes(const std::vector<std::string> &includes) { void inject_includes(const std::vector<std::string> &includes) {
if (!includes.empty()) { if (!includes.empty()) {
for (const auto &file : includes) { for (const auto &file : includes) {
if (!fs::is_regular_file(file)) { if (!fs::exists(path / file)) {
throw std::runtime_error("Include '" + file + "' does not exist"); throw std::runtime_error("Include not found: " + file);
} }
cmd("include")(file); cmd("include")(file);
} }
@ -387,7 +493,7 @@ struct Generator {
throw std::runtime_error("Detected additional \" at the end of cmake block"); throw std::runtime_error("Detected additional \" at the end of cmake block");
} }
auto cmake_lf = tolf(cmake); auto cmake_lf = tolf(cmake);
while (cmake_lf.back() == '\n') while (!cmake_lf.empty() && cmake_lf.back() == '\n')
cmake_lf.pop_back(); cmake_lf.pop_back();
bool did_indent = false; bool did_indent = false;
for (char ch : cmake_lf) { for (char ch : cmake_lf) {
@ -456,28 +562,26 @@ struct ConditionScope {
}; };
static bool vcpkg_valid_identifier(const std::string &name) { static bool vcpkg_valid_identifier(const std::string &name) {
// 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]+)* // [a-z0-9]+(-[a-z0-9]+)*
auto is_identifier = [](const std::string &s) { for (size_t i = 0; i < name.length(); i++) {
for (size_t i = 0; i < s.length(); i++) { auto c = name[i];
auto c = s[i]; if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (i > 0 && c == '-')) {
if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (i > 0 && c == '-')) { continue;
continue;
}
return false;
} }
return false;
}
return true;
}
static bool vcpkg_identifier_reserved(const std::string &name) {
// prn|aux|nul|con|lpt[1-9]|com[1-9]|core|default
if (name == "prn" || name == "aux" || name == "nul" || name == "con" || name == "core" || name == "default") {
return true; return true;
}; }
return is_identifier(name) && !is_reserved(name); if (name.length() == 4 && (name.compare(0, 3, "lpt") == 0 || name.compare(0, 3, "com") == 0) && (name[3] >= '1' && name[3] <= '9')) {
return true;
}
return false;
} }
static std::string vcpkg_escape_identifier(const std::string &name) { static std::string vcpkg_escape_identifier(const std::string &name) {
@ -494,10 +598,12 @@ static std::string vcpkg_escape_identifier(const std::string &name) {
escaped += std::tolower(ch); escaped += std::tolower(ch);
} }
if (!vcpkg_valid_identifier(escaped)) { if (!vcpkg_valid_identifier(escaped)) {
throw std::runtime_error("The escaped project name '" + escaped + "' is not usable with [vcpkg]"); throw std::runtime_error("The escaped project name '" + escaped + "' is not usable with [vcpkg]");
} }
if (vcpkg_identifier_reserved(escaped)) {
throw std::runtime_error("The escaped project name '" + escaped + "' is a reserved name [vcpkg]");
}
return escaped; return escaped;
} }
@ -510,7 +616,18 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
auto is_root_project = parent_project == nullptr; auto is_root_project = parent_project == nullptr;
parser::Project project(parent_project, path, false); parser::Project project(parent_project, path, false);
Generator gen(project);
for (auto const &lang : project.project_languages) {
if (known_languages.find(lang) == known_languages.end()) {
if (project.project_allow_unknown_languages) {
printf("[warning] Unknown language '%s' specified\n", lang.c_str());
} else {
throw std::runtime_error("Unknown language '" + lang + "' specified");
}
}
}
Generator gen(project, path);
// Helper lambdas for more convenient CMake generation // Helper lambdas for more convenient CMake generation
auto &ss = gen.ss; auto &ss = gen.ss;
@ -616,7 +733,13 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
if (!project.options.empty()) { if (!project.options.empty()) {
comment("Options"); comment("Options");
for (const auto &opt : project.options) { for (const auto &opt : project.options) {
cmd("option")(opt.name, RawArg(Command::quote(opt.comment)), opt.val ? "ON" : "OFF"); std::string default_val;
if (opt.value.index() == 0) {
default_val = mpark::get<0>(opt.value) ? "ON" : "OFF";
} else {
default_val = mpark::get<1>(opt.value);
}
cmd("option")(opt.name, RawArg(Command::quote(opt.help)), default_val);
} }
endl(); endl();
} }
@ -625,16 +748,16 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
comment("Variables"); comment("Variables");
for (const auto &set : project.variables) { for (const auto &set : project.variables) {
std::string set_val; std::string set_val;
if (set.val.index() == 1) { if (set.value.index() == 1) {
set_val = mpark::get<1>(set.val); set_val = mpark::get<1>(set.value);
} else { } else {
set_val = mpark::get<0>(set.val) ? "ON" : "OFF"; set_val = mpark::get<0>(set.value) ? "ON" : "OFF";
} }
if (set.cache) { if (set.cache) {
auto typ = set.val.index() == 1 ? "STRING" : "BOOL"; auto typ = set.value.index() == 1 ? "STRING" : "BOOL";
auto force = set.force ? "FORCE" : ""; auto force = set.force ? "FORCE" : "";
cmd("set")(set.name, set_val, typ, set.comment, force); cmd("set")(set.name, set_val, typ, set.help, force);
} else { } else {
cmd("set")(set.name, set_val); cmd("set")(set.name, set_val);
} }
@ -662,7 +785,11 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
gen.conditional_includes(project.include_after); gen.conditional_includes(project.include_after);
gen.conditional_cmake(project.cmake_after); gen.conditional_cmake(project.cmake_after);
if (!project.vcpkg.packages.empty()) { if (project.vcpkg.enabled()) {
if (!is_root_project) {
throw std::runtime_error("[vcpkg] is only supported in the root project");
}
// Allow the user to specify a url or derive it from the version // Allow the user to specify a url or derive it from the version
auto url = project.vcpkg.url; auto url = project.vcpkg.url;
auto version_name = url; auto version_name = url;
@ -675,7 +802,8 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
} }
// Show a nicer error than vcpkg when specifying an invalid package name // Show a nicer error than vcpkg when specifying an invalid package name
for (const auto &package : project.vcpkg.packages) { const auto &packages = project.vcpkg.packages;
for (const auto &package : packages) {
if (!vcpkg_valid_identifier(package.name)) { if (!vcpkg_valid_identifier(package.name)) {
throw std::runtime_error("Invalid [vcpkg].packages name '" + package.name + "' (needs to be lowercase alphanumeric)"); throw std::runtime_error("Invalid [vcpkg].packages name '" + package.name + "' (needs to be lowercase alphanumeric)");
} }
@ -685,14 +813,20 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
// clang-format off // clang-format off
cmd("if")("CMKR_ROOT_PROJECT", "AND", "NOT", "CMKR_DISABLE_VCPKG"); cmd("if")("CMKR_ROOT_PROJECT", "AND", "NOT", "CMKR_DISABLE_VCPKG");
cmd("include")("FetchContent"); cmd("include")("FetchContent");
cmd("message")("STATUS", "Fetching vcpkg (" + version_name + ")..."); comment("Fix warnings about DOWNLOAD_EXTRACT_TIMESTAMP");
cmd("FetchContent_Declare")("vcpkg", "URL", url); // clang-format off
// Not using FetchContent_MakeAvailable here in case vcpkg adds CMakeLists.txt cmd("if")("POLICY", "CMP0135");
cmd("FetchContent_GetProperties")("vcpkg"); cmd("cmake_policy")("SET", "CMP0135", "NEW");
cmd("if")("NOT", "vcpkg_POPULATED");
cmd("FetchContent_Populate")("vcpkg");
cmd("include")("${vcpkg_SOURCE_DIR}/scripts/buildsystems/vcpkg.cmake");
cmd("endif")(); cmd("endif")();
// clang-format on
cmd("message")("STATUS", "Fetching vcpkg (" + version_name + ")...");
cmd("FetchContent_Declare")("vcpkg", "URL", url);
// Not using FetchContent_MakeAvailable here in case vcpkg adds CMakeLists.txt
cmd("FetchContent_GetProperties")("vcpkg");
cmd("if")("NOT", "vcpkg_POPULATED");
cmd("FetchContent_Populate")("vcpkg");
cmd("include")("${vcpkg_SOURCE_DIR}/scripts/buildsystems/vcpkg.cmake");
cmd("endif")();
cmd("endif")(); cmd("endif")();
endl(); endl();
// clang-format on // clang-format on
@ -709,16 +843,21 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
"dependencies": [ "dependencies": [
)"; )";
const auto &packages = project.vcpkg.packages;
for (size_t i = 0; i < packages.size(); i++) { for (size_t i = 0; i < packages.size(); i++) {
const auto &package = packages[i]; const auto &package = packages[i];
const auto &features = package.features; const auto &features = package.features;
if (!vcpkg_valid_identifier(package.name)) { if (!vcpkg_valid_identifier(package.name)) {
throw std::runtime_error("Invalid vcpkg package name '" + package.name + "'"); throw std::runtime_error("Invalid vcpkg package name '" + package.name + "', name is not valid");
}
if (vcpkg_identifier_reserved(package.name)) {
throw std::runtime_error("Invalid vcpkg package name '" + package.name + "', name is reserved");
} }
for (const auto &feature : features) { for (const auto &feature : features) {
if (!vcpkg_valid_identifier(feature)) { if (!vcpkg_valid_identifier(feature)) {
throw std::runtime_error("Invalid vcpkg package feature '" + feature + "'"); throw std::runtime_error("Invalid vcpkg package feature '" + feature + "', name is not valid");
}
if (vcpkg_identifier_reserved(feature)) {
throw std::runtime_error("Invalid vcpkg package feature '" + feature + "', name is reserved");
} }
} }
if (features.empty()) { if (features.empty()) {
@ -763,20 +902,32 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
if (!project.contents.empty()) { if (!project.contents.empty()) {
cmd("include")("FetchContent").endl(); cmd("include")("FetchContent").endl();
if (!project.root()->vcpkg.enabled()) {
comment("Fix warnings about DOWNLOAD_EXTRACT_TIMESTAMP");
// clang-format off
cmd("if")("POLICY", "CMP0135");
cmd("cmake_policy")("SET", "CMP0135", "NEW");
cmd("endif")();
// clang-format on
}
for (const auto &content : project.contents) { for (const auto &content : project.contents) {
ConditionScope cs(gen, content.condition); ConditionScope cs(gen, content.condition);
gen.conditional_includes(content.include_before); gen.conditional_includes(content.include_before);
gen.conditional_cmake(content.cmake_before); gen.conditional_cmake(content.cmake_before);
std::string version_info = ""; std::string version_info;
if (content.arguments.contains("GIT_TAG")) { if (content.arguments.contains("GIT_TAG")) {
version_info = " (" + content.arguments.at("GIT_TAG") + ")"; version_info = " (" + content.arguments.at("GIT_TAG") + ")";
} else if (content.arguments.contains("SVN_REVISION")) { } else if (content.arguments.contains("SVN_REVISION")) {
version_info = " (" + content.arguments.at("SVN_REVISION") + ")"; version_info = " (" + content.arguments.at("SVN_REVISION") + ")";
} }
cmd("message")("STATUS", "Fetching " + content.name + version_info + "..."); cmd("message")("STATUS", "Fetching " + content.name + version_info + "...");
cmd("FetchContent_Declare")(content.name, content.arguments); if (content.system) {
cmd("FetchContent_Declare")(content.name, "SYSTEM", content.arguments);
} else {
cmd("FetchContent_Declare")(content.name, content.arguments);
}
cmd("FetchContent_MakeAvailable")(content.name).endl(); cmd("FetchContent_MakeAvailable")(content.name).endl();
gen.conditional_includes(content.include_after); gen.conditional_includes(content.include_after);
@ -837,10 +988,38 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
gen.conditional_cmake(subdir.cmake_after); gen.conditional_cmake(subdir.cmake_after);
} }
// The implicit default is ["C", "CXX"], so make sure this list isn't
// empty or projects without languages explicitly defined will error.
auto project_languages = project.project_languages;
if (project_languages.empty())
project_languages = {"C", "CXX"};
// All acceptable extensions based off our given languages.
tsl::ordered_set<std::string> project_extensions;
for (const auto &language : project_languages) {
auto itr = known_languages.find(language);
if (itr != known_languages.end()) {
project_extensions.insert(itr->second.begin(), itr->second.end());
}
}
auto contains_language_source = [&project_extensions](const std::vector<std::string> &sources) {
for (const auto &source : sources) {
auto extension = fs::path(source).extension().string();
if (project_extensions.count(extension) > 0) {
return true;
}
}
return false;
};
if (!project.targets.empty()) { if (!project.targets.empty()) {
auto project_root = project.root(); auto project_root = project.root();
for (size_t i = 0; i < project.targets.size(); i++) { for (size_t i = 0; i < project.targets.size(); i++) {
const auto &target = project.targets[i]; const auto &target = project.targets[i];
auto throw_target_error = [&target](const std::string &message) { throw std::runtime_error("[target." + target.name + "] " + message); };
const parser::Template *tmplate = nullptr; const parser::Template *tmplate = nullptr;
std::unique_ptr<ConditionScope> tmplate_cs{}; std::unique_ptr<ConditionScope> tmplate_cs{};
@ -944,12 +1123,46 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
auto sources = expand_cmake_paths(condition_sources, path, is_root_project); auto sources = expand_cmake_paths(condition_sources, path, is_root_project);
if (sources.empty()) { if (sources.empty()) {
auto source_key = condition.empty() ? "sources" : (condition + ".sources"); auto source_key = condition.empty() ? "sources" : (condition + ".sources");
throw std::runtime_error(target.name + " " + source_key + " wildcard found 0 files"); throw_target_error(source_key + " wildcard found 0 files");
}
// Make sure there are source files for the languages used by the project
switch (target.type) {
case parser::target_executable:
case parser::target_library:
case parser::target_shared:
case parser::target_static:
case parser::target_object:
if (!contains_language_source(sources)) {
std::string extensions;
for (const auto &language : project_extensions) {
if (!extensions.empty()) {
extensions += " ";
}
extensions += language;
}
throw_target_error("No sources found with valid extensions (" + extensions + ")");
}
// Make sure relative source files exist
for (const auto &source : sources) {
auto var_index = source.find("${");
if (var_index != std::string::npos)
continue;
const auto &source_path = fs::path(path) / source;
if (!fs::exists(source_path)) {
throw_target_error("Source file not found: " + source);
}
}
break;
default:
break;
} }
if (sources_with_set) { if (sources_with_set) {
// This is a sanity check to make sure the unconditional sources are first // This is a sanity check to make sure the unconditional sources are first
if (!condition.empty()) { if (!condition.empty()) {
throw std::runtime_error("Unreachable code, make sure unconditional sources are first"); throw_target_error("Unreachable code, make sure unconditional sources are first");
} }
cmd("set")(sources_var, sources); cmd("set")(sources_var, sources);
sources_with_set = false; sources_with_set = false;
@ -962,7 +1175,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
if (tmplate != nullptr) { if (tmplate != nullptr) {
if (target_type != parser::target_template) { if (target_type != parser::target_template) {
throw std::runtime_error("Unreachable code, unexpected target type for template"); throw_target_error("Unreachable code, unexpected target type for template");
} }
target_type = tmplate->outline.type; target_type = tmplate->outline.type;
} }
@ -1010,7 +1223,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
target_scope = "PUBLIC"; target_scope = "PUBLIC";
break; break;
default: default:
throw std::runtime_error("Unimplemented enum value"); throw_target_error("Unimplemented enum value");
} }
// Handle custom add commands from templates. // Handle custom add commands from templates.
@ -1091,7 +1304,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
for (const auto &propItr : properties) { for (const auto &propItr : properties) {
if (propItr.first == "MSVC_RUNTIME_LIBRARY") { if (propItr.first == "MSVC_RUNTIME_LIBRARY") {
if (project_root->project_msvc_runtime == parser::msvc_last) { if (project_root->project_msvc_runtime == parser::msvc_last) {
throw std::runtime_error("You cannot set [target].msvc-runtime without setting the root [project].msvc-runtime"); throw_target_error("You cannot set msvc-runtime without setting the root [project].msvc-runtime");
} }
} }
} }
@ -1134,7 +1347,12 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
} }
auto working_directory = std::make_pair("WORKING_DIRECTORY", dir); auto working_directory = std::make_pair("WORKING_DIRECTORY", dir);
auto command = std::make_pair("COMMAND", test.command); auto command = std::make_pair("COMMAND", test.command);
auto arguments = std::make_pair("", test.arguments);
// Transform the provided arguments into raw arguments to prevent them from being quoted when the generator runs
std::vector<RawArg> raw_arguments{};
for (const auto &argument : test.arguments)
raw_arguments.emplace_back(argument);
auto arguments = std::make_pair("", raw_arguments);
ConditionScope cs(gen, test.condition); ConditionScope cs(gen, test.condition);
cmd("add_test")(name, configurations, working_directory, command, arguments).endl(); cmd("add_test")(name, configurations, working_directory, command, arguments).endl();
} }
@ -1165,10 +1383,19 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
} }
} }
// Fetch the generated CMakeLists.txt output from the stringstream buffer
auto generated_cmake = ss.str();
// Make sure the file ends in a single newline
while (!generated_cmake.empty() && std::isspace(generated_cmake.back())) {
generated_cmake.pop_back();
}
generated_cmake += '\n';
// Generate CMakeLists.txt // Generate CMakeLists.txt
auto list_path = fs::path(path) / "CMakeLists.txt"; auto list_path = fs::path(path) / "CMakeLists.txt";
auto should_regenerate = [&list_path, &ss]() { auto should_regenerate = [&list_path, &generated_cmake]() {
if (!fs::exists(list_path)) if (!fs::exists(list_path))
return true; return true;
@ -1178,11 +1405,11 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
} }
std::string data((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>()); std::string data((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
return data != ss.str(); return data != generated_cmake;
}(); }();
if (should_regenerate) { if (should_regenerate) {
create_file(list_path, ss.str()); create_file(list_path, generated_cmake);
} }
auto generate_subdir = [path, &project](const fs::path &sub) { auto generate_subdir = [path, &project](const fs::path &sub) {

@ -1,31 +0,0 @@
#include "error.hpp"
#include <cassert>
#include <cstddef>
namespace cmkr {
namespace error {
Status::Status(Code ec) noexcept : ec_(ec) {
}
Status::operator int() const noexcept {
return static_cast<int>(ec_);
}
Status::Code Status::code() const noexcept {
return ec_;
}
} // namespace error
} // namespace cmkr
// strings for cmkr::error::Status::Code
static const char *err_string[] = {
"Success", "Runtime error", "Initialization error", "CMake generation error", "Build error", "Clean error", "Install error",
};
const char *cmkr_error_status(int i) {
assert(i >= 0 && static_cast<size_t>(i) < (sizeof(err_string) / sizeof(*(err_string))));
return err_string[i];
}

@ -23,11 +23,3 @@ arguments:
} }
} // namespace help } // namespace help
} // namespace cmkr } // namespace cmkr
const char *cmkr_help_version(void) {
return cmkr::help::version();
}
const char *cmkr_help_message(void) {
return cmkr::help::message();
}

@ -36,7 +36,7 @@ static std::string format_key_message(const std::string &message, const toml::ke
auto loc = value.location(); auto loc = value.location();
auto line_number_str = std::to_string(loc.line()); auto line_number_str = std::to_string(loc.line());
auto line_width = line_number_str.length(); auto line_width = line_number_str.length();
auto line_str = loc.line_str(); const auto &line_str = loc.line_str();
std::ostringstream oss; std::ostringstream oss;
oss << message << "\n"; oss << message << "\n";
@ -73,7 +73,7 @@ class TomlChecker {
public: public:
TomlChecker(const TomlBasicValue &v, const toml::key &ky) : m_v(toml::find(v, ky)) { TomlChecker(const TomlBasicValue &v, const toml::key &ky) : m_v(toml::find(v, ky)) {
} }
TomlChecker(const TomlBasicValue &v) : m_v(v) { explicit TomlChecker(const TomlBasicValue &v) : m_v(v) {
} }
TomlChecker(const TomlChecker &) = delete; TomlChecker(const TomlChecker &) = delete;
TomlChecker(TomlChecker &&) = delete; TomlChecker(TomlChecker &&) = delete;
@ -175,7 +175,7 @@ class TomlCheckerRoot {
bool m_checked = false; bool m_checked = false;
public: public:
TomlCheckerRoot(const TomlBasicValue &root) : m_root(root) { explicit TomlCheckerRoot(const TomlBasicValue &root) : m_root(root) {
} }
TomlCheckerRoot(const TomlCheckerRoot &) = delete; TomlCheckerRoot(const TomlCheckerRoot &) = delete;
TomlCheckerRoot(TomlCheckerRoot &&) = delete; TomlCheckerRoot(TomlCheckerRoot &&) = delete;
@ -265,8 +265,12 @@ Project::Project(const Project *parent, const std::string &path, bool build) : p
conditions["bsd"] = R"cmake(CMAKE_SYSTEM_NAME MATCHES "BSD")cmake"; conditions["bsd"] = R"cmake(CMAKE_SYSTEM_NAME MATCHES "BSD")cmake";
conditions["linux"] = conditions["lunix"] = R"cmake(CMAKE_SYSTEM_NAME MATCHES "Linux")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["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["msvc"] = R"cmake(MSVC)cmake";
conditions["clang"] =
R"cmake((CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND NOT CMAKE_CXX_COMPILER_FRONTEND_VARIANT MATCHES "^MSVC$") OR (CMAKE_C_COMPILER_ID MATCHES "Clang" AND NOT CMAKE_C_COMPILER_FRONTEND_VARIANT MATCHES "^MSVC$"))cmake";
conditions["clang-cl"] =
R"cmake((CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT MATCHES "^MSVC$") OR (CMAKE_C_COMPILER_ID MATCHES "Clang" AND CMAKE_C_COMPILER_FRONTEND_VARIANT MATCHES "^MSVC$"))cmake";
conditions["clang-any"] = R"cmake(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_C_COMPILER_ID MATCHES "Clang")cmake";
conditions["root"] = R"cmake(CMKR_ROOT_PROJECT)cmake"; conditions["root"] = R"cmake(CMKR_ROOT_PROJECT)cmake";
conditions["x64"] = R"cmake(CMAKE_SIZEOF_VOID_P EQUAL 8)cmake"; conditions["x64"] = R"cmake(CMAKE_SIZEOF_VOID_P EQUAL 8)cmake";
conditions["x32"] = R"cmake(CMAKE_SIZEOF_VOID_P EQUAL 4)cmake"; conditions["x32"] = R"cmake(CMAKE_SIZEOF_VOID_P EQUAL 4)cmake";
@ -288,6 +292,7 @@ Project::Project(const Project *parent, const std::string &path, bool build) : p
project.optional("version", project_version); project.optional("version", project_version);
project.optional("description", project_description); project.optional("description", project_description);
project.optional("languages", project_languages); project.optional("languages", project_languages);
project.optional("allow-unknown-languages", project_allow_unknown_languages);
project.optional("cmake-before", cmake_before); project.optional("cmake-before", cmake_before);
project.optional("cmake-after", cmake_after); project.optional("cmake-after", cmake_after);
project.optional("include-before", include_before); project.optional("include-before", include_before);
@ -345,18 +350,18 @@ Project::Project(const Project *parent, const std::string &path, bool build) : p
s.name = itr.first; s.name = itr.first;
const auto &value = itr.second; const auto &value = itr.second;
if (value.is_boolean()) { if (value.is_boolean()) {
s.val = value.as_boolean(); s.value = value.as_boolean();
} else if (value.is_string()) { } else if (value.is_string()) {
s.val = value.as_string(); s.value = value.as_string();
} else { } else {
auto &setting = checker.create(value); auto &setting = checker.create(value);
setting.optional("comment", s.comment); setting.optional("help", s.help);
if (setting.contains("value")) { if (setting.contains("value")) {
const auto &v = setting.find("value"); const auto &v = setting.find("value");
if (v.is_boolean()) { if (v.is_boolean()) {
s.val = v.as_boolean(); s.value = v.as_boolean();
} else { } else {
s.val = v.as_string(); s.value = v.as_string();
} }
} }
setting.optional("cache", s.cache); setting.optional("cache", s.cache);
@ -374,13 +379,37 @@ Project::Project(const Project *parent, const std::string &path, bool build) : p
o.name = itr.first; o.name = itr.first;
const auto &value = itr.second; const auto &value = itr.second;
if (value.is_boolean()) { if (value.is_boolean()) {
o.val = value.as_boolean(); o.value = value.as_boolean();
} else { } else if (value.is_string()) {
auto str = std::string(value.as_string());
if (str == "root") {
o.value = std::string("${CMKR_ROOT_PROJECT}");
} else {
throw_key_error("Unsupported option value '" + str + "'", str, value);
}
} else if (value.is_table()) {
auto &option = checker.create(value); auto &option = checker.create(value);
option.optional("comment", o.comment); option.optional("help", o.help);
option.optional("value", o.val); if (option.contains("value")) {
const auto &ovalue = option.find("value");
if (ovalue.is_boolean()) {
o.value = ovalue.as_boolean();
} else if (ovalue.is_string()) {
auto str = std::string(ovalue.as_string());
if (str == "root") {
o.value = std::string("${CMKR_ROOT_PROJECT}");
} else {
throw_key_error("Unsupported option value '" + str + "'", str, value);
}
} else {
throw_key_error(toml::concat_to_string("Unsupported value type: ", ovalue.type()), "value", value);
}
}
} else {
throw_key_error(toml::concat_to_string("Unsupported value type: ", itr.second.type()), itr.first, itr.second);
} }
options.push_back(o); options.push_back(o);
conditions.emplace(o.name, o.name);
} }
} }
@ -417,6 +446,15 @@ Project::Project(const Project *parent, const std::string &path, bool build) : p
c.optional("cmake-after", content.cmake_after); c.optional("cmake-after", content.cmake_after);
c.optional("include-before", content.include_before); c.optional("include-before", content.include_before);
c.optional("include-after", content.include_after); c.optional("include-after", content.include_after);
c.optional("system", content.system);
// Check if the minimum version requirement is satisfied (CMake 3.25)
if (c.contains("system") && !this->cmake_minimum_version(3, 25)) {
throw_key_error("The system argument is only supported on CMake version 3.25 and above.\nSet the CMake version in cmake.toml:\n"
"[cmake]\n"
"version = \"3.25\"\n",
"system", "");
}
for (const auto &argItr : itr.second.as_table()) { for (const auto &argItr : itr.second.as_table()) {
std::string value; std::string value;
@ -559,6 +597,38 @@ Project::Project(const Project *parent, const std::string &path, bool build) : p
t.optional("link-libraries", target.link_libraries); t.optional("link-libraries", target.link_libraries);
t.optional("private-link-libraries", target.private_link_libraries); t.optional("private-link-libraries", target.private_link_libraries);
// Add support for relative paths for (private-)link-libraries
const auto fix_relative_paths = [&name, &path](ConditionVector &libraries, const char *key) {
for (const auto &library_entries : libraries) {
for (auto &library_path : libraries[library_entries.first]) {
// Skip processing paths with potential CMake macros in them (this check isn't perfect)
// https://cmake.org/cmake/help/latest/manual/cmake-language.7.html#variable-references
if ((library_path.find("${") != std::string::npos || library_path.find("$ENV{") != std::string::npos ||
library_path.find("$CACHE{") != std::string::npos) &&
library_path.find('}') != std::string::npos) {
continue;
}
// Skip paths that don't contain backwards or forwards slashes
if (library_path.find_first_of(R"(\/)") == std::string::npos) {
continue;
}
// Check if the new file path exists, otherwise emit an error
const auto expected_library_file_path = fs::path{path} / library_path;
if (!fs::exists(expected_library_file_path)) {
throw std::runtime_error("Attempted to link against a library file that doesn't exist for target \"" + name + "\" in \"" +
key + "\": " + library_path);
}
// Prepend ${CMAKE_CURRENT_SOURCE_DIR} to the path
library_path.insert(0, "${CMAKE_CURRENT_SOURCE_DIR}/");
}
}
};
fix_relative_paths(target.link_libraries, "link-libraries");
fix_relative_paths(target.private_link_libraries, "private-link-libraries");
t.optional("link-options", target.link_options); t.optional("link-options", target.link_options);
t.optional("private-link-options", target.private_link_options); t.optional("private-link-options", target.private_link_options);
@ -659,6 +729,7 @@ Project::Project(const Project *parent, const std::string &path, bool build) : p
t.optional("add-function", tmplate.add_function); t.optional("add-function", tmplate.add_function);
t.optional("pass-sources-to-add-function", tmplate.pass_sources_to_add_function); t.optional("pass-sources-to-add-function", tmplate.pass_sources_to_add_function);
t.optional("pass-sources", tmplate.pass_sources_to_add_function);
templates.push_back(tmplate); templates.push_back(tmplate);
} }
@ -741,6 +812,25 @@ const Project *Project::root() const {
return root; return root;
} }
bool Project::cmake_minimum_version(int major, int minor) const {
// NOTE: this code is like pulling teeth, sorry
auto root_version = root()->cmake_version;
auto range_index = root_version.find("...");
if (range_index != std::string::npos) {
root_version.resize(range_index);
}
auto period_index = root_version.find('.');
auto root_major = atoi(root_version.substr(0, period_index).c_str());
int root_minor = 0;
if (period_index != std::string::npos) {
auto end_index = root_version.find('.', period_index + 1);
root_minor = atoi(root_version.substr(period_index + 1, end_index).c_str());
}
return std::tie(root_major, root_minor) >= std::tie(major, minor);
}
bool is_root_path(const std::string &path) { bool is_root_path(const std::string &path) {
const auto toml_path = fs::path(path) / "cmake.toml"; const auto toml_path = fs::path(path) / "cmake.toml";
if (!fs::exists(toml_path)) { if (!fs::exists(toml_path)) {

22
tests/CMakeLists.txt generated

@ -100,3 +100,25 @@ if(MSVC) # msvc
) )
endif() endif()
add_test(
NAME
compile-options
WORKING_DIRECTORY
"${CMAKE_CURRENT_LIST_DIR}/compile-options"
COMMAND
"$<TARGET_FILE:cmkr>"
build
)
if(WIN32) # windows
add_test(
NAME
relative-paths
WORKING_DIRECTORY
"${CMAKE_CURRENT_LIST_DIR}/relative-paths"
COMMAND
"$<TARGET_FILE:cmkr>"
build
)
endif()

@ -8,4 +8,4 @@ description = "Minimal example"
type = "executable" type = "executable"
sources = ["src/basic.cpp"] 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)`. # 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)`.

@ -51,4 +51,17 @@ condition = "msvc"
name = "msvc-runtime" name = "msvc-runtime"
working-directory = "msvc-runtime" working-directory = "msvc-runtime"
command = "$<TARGET_FILE:cmkr>" command = "$<TARGET_FILE:cmkr>"
arguments = ["build"] arguments = ["build"]
[[test]]
name = "compile-options"
working-directory = "compile-options"
command = "$<TARGET_FILE:cmkr>"
arguments = ["build"]
[[test]]
condition = "windows"
name = "relative-paths"
working-directory = "relative-paths"
command = "$<TARGET_FILE:cmkr>"
arguments = ["build"]

@ -0,0 +1,15 @@
# Example project that sets compiler/linker flags for various platforms.
[project]
name = "compile-options"
description = "Compiler flags"
[target.hello]
type = "executable"
sources = ["src/main.cpp"]
msvc.compile-options = ["/W2"]
gcc.compile-options = ["-Wall"]
clang.compile-options = ["-Wall"]
# The `hello` target uses [conditions](/cmake-toml#conditions) to set different compiler flags depending on the platform. See the [targets](/cmake-toml/#targets) documentation for other things you can set.
# _Note_: In general you only want to specify flags _required_ to compile your code without errors.

@ -0,0 +1,5 @@
#include <cstdio>
int main() {
puts("Hello from cmkr!");
}

@ -1,23 +1,23 @@
[project] [project]
name = "conditions" name = "conditions"
cmake-after = "set(CUSTOM ON)" cmake-after = "set(CUSTOM ON)"
[conditions] [conditions]
custom = "CUSTOM" custom = "CUSTOM"
[target.example] [target.example]
type = "executable" type = "executable"
sources = ["src/main.cpp"] sources = ["src/main.cpp"]
windows.sources = ["src/windows_specific.cpp"] windows.sources = ["src/windows_specific.cpp"]
cmake-after = "message(STATUS cmake-after)" cmake-after = "message(STATUS cmake-after)"
windows.cmake-after = "message(STATUS win32-after)" windows.cmake-after = "message(STATUS win32-after)"
macos.cmake-after = "message(STATUS macos-after)" macos.cmake-after = "message(STATUS macos-after)"
linux.cmake-after = "message(STATUS linux-after)" linux.cmake-after = "message(STATUS linux-after)"
unix.cmake-after = "message(STATUS unix-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] [target.example.properties]
AUTOMOC = false AUTOMOC = false
custom.OUTPUT_NAME = "example2" custom.OUTPUT_NAME = "example2"
custom.AUTORCC = true custom.AUTORCC = true
AUTOGEN = "ON" AUTOGEN = "ON"

@ -1 +1,2 @@
int main() { } int main() {
}

@ -1,3 +1,4 @@
#include <Windows.h> #include <Windows.h>
void foo() { } void foo() {
}

@ -1,12 +1,12 @@
# Require a C++11 compiler for the target `example`. # Require a C++11 compiler for the target `example`.
[project] [project]
name = "cxx-standard" name = "cxx-standard"
description = "Changing C++ standard" description = "Changing C++ standard"
[target.example] [target.example]
type = "executable" type = "executable"
sources = ["src/main.cpp"] sources = ["src/main.cpp"]
compile-features = ["cxx_std_11"] 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). # 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).

@ -1,8 +1,7 @@
#include <cstdio> #include <cstdio>
#include <tuple> #include <tuple>
int main() int main() {
{ auto tpl = std::make_tuple(1, 2);
auto tpl = std::make_tuple(1, 2); printf("Hello from C++11 %d\n", std::get<0>(tpl));
printf("Hello from C++11 %d\n", std::get<0>(tpl)); }
}

@ -12,4 +12,4 @@ type = "executable"
sources = ["src/main.cpp"] sources = ["src/main.cpp"]
link-libraries = ["fmt::fmt"] link-libraries = ["fmt::fmt"]
# This is equivalent to calling CMake's [FetchContent](https://cmake.org/cmake/help/latest/module/FetchContent.html). # This is equivalent to calling CMake's [FetchContent](https://cmake.org/cmake/help/latest/module/FetchContent.html).

@ -1,6 +1,5 @@
#include <fmt/core.h> #include <fmt/core.h>
int main() int main() {
{
fmt::print("Hello, world!\n"); fmt::print("Hello, world!\n");
} }

@ -1,18 +1,18 @@
[project] [project]
name = "globbing" name = "globbing"
description = "Globbing sources" description = "Globbing sources"
# Recursively glob in the mylib/ folder # Recursively glob in the mylib/ folder
[target.mylib] [target.mylib]
type = "static" type = "static"
alias = "mylib::mylib" alias = "mylib::mylib"
sources = ["mylib/**.hpp", "mylib/**.cpp"] sources = ["mylib/**.hpp", "mylib/**.cpp"]
include-directories = ["mylib/include"] include-directories = ["mylib/include"]
# Single-folder glob in example/src/ # Single-folder glob in example/src/
[target.example] [target.example]
type = "executable" type = "executable"
sources = ["example/src/*.cpp"] sources = ["example/src/*.cpp"]
link-libraries = ["mylib::mylib"] 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. # 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.

@ -1,7 +1,7 @@
#include <iostream> #include <iostream>
#include <mylib/mylib.hpp> #include <mylib/mylib.hpp>
int main() { int main() {
std::cout << mylib::message() << std::endl; std::cout << mylib::message() << std::endl;
return 0; return 0;
} }

@ -1,8 +1,7 @@
#pragma once #pragma once
#include <string> #include <string>
namespace mylib namespace mylib {
{ std::string message();
std::string message(); }
}

@ -1,6 +1,5 @@
#include <mylib/mylib.hpp> #include <mylib/mylib.hpp>
std::string mylib::message() std::string mylib::message() {
{ return "cmkr is awesome!";
return "cmkr is awesome!"; }
}

@ -1,13 +1,13 @@
[project] [project]
name = "interface" name = "interface"
description = "Header-only library" description = "Header-only library"
[target.mylib] [target.mylib]
type = "interface" type = "interface"
include-directories = ["include"] include-directories = ["include"]
compile-features = ["cxx_std_11"] compile-features = ["cxx_std_11"]
[target.example] [target.example]
type = "executable" type = "executable"
sources = ["src/main.cpp"] sources = ["src/main.cpp"]
link-libraries = ["mylib"] link-libraries = ["mylib"]

@ -1,4 +1,5 @@
namespace mylib namespace mylib {
{ static const char *version() {
static const char* version() { return "v1.0"; } return "v1.0";
} // namespace mylib }
} // namespace mylib

@ -1,8 +1,7 @@
#include <cstdio> #include <cstdio>
#include "mylib/mylib.hpp" #include "mylib/mylib.hpp"
int main() int main() {
{ printf("mylib version: %s\n", mylib::version());
printf("mylib version: %s\n", mylib::version()); }
}

@ -12,4 +12,4 @@ sources = ["src/main.cpp"]
[target.dynamic-runtime] [target.dynamic-runtime]
type = "executable" type = "executable"
sources = ["src/main.cpp"] sources = ["src/main.cpp"]
msvc-runtime = "dynamic" msvc-runtime = "dynamic"

@ -0,0 +1,11 @@
[project]
name = "relative-paths"
[target.test-library]
type = "static"
sources = ["src/library-code.cpp"]
[target.example]
type = "executable"
sources = ["src/main.cpp"]
windows.link-libraries = ["libs/test-library-x64-Release.lib"]

@ -0,0 +1,6 @@
// Created by Anthony Printup on 9/18/2023.
#include <cstdio>
extern "C" void library_function() {
std::puts("Hello from library_function!");
}

@ -0,0 +1,12 @@
// Created by Anthony Printup on 9/18/2023.
#include <cstdio>
#ifdef WIN32
extern "C" void library_function();
#endif
int main() {
puts("Hello from cmkr(relative-paths)!");
#ifdef WIN32
library_function();
#endif
}

@ -22,4 +22,4 @@ compile-definitions = ["APP_A"]
type = "app" type = "app"
compile-definitions = ["APP_B"] compile-definitions = ["APP_B"]
# **Note**: In most cases you probably want to use an [interface](/examples/interface) target instead. # **Note**: In most cases you probably want to use an [interface](/examples/interface) target instead.

@ -19,4 +19,4 @@ sources = ["src/main.cpp"]
link-libraries = ["fmt::fmt"] link-libraries = ["fmt::fmt"]
# The bootstrapping of vcpkg is fully automated and no user interaction is necessary. You can disable vcpkg by setting `CMKR_DISABLE_VCPKG=ON`. # The bootstrapping of vcpkg is fully automated and no user interaction is necessary. You can disable vcpkg by setting `CMKR_DISABLE_VCPKG=ON`.
# To specify package features you can use the following syntax: `imgui[docking-experimental,freetype,sdl2-binding,opengl3-binding]`. # To specify package features you can use the following syntax: `imgui[docking-experimental,freetype,sdl2-binding,opengl3-binding]`.

@ -1,6 +1,5 @@
#include <fmt/core.h> #include <fmt/core.h>
int main() int main() {
{
fmt::print("Hello, world!\n"); fmt::print("Hello, world!\n");
} }

@ -0,0 +1,3 @@
# Disable clang-format in this folder
DisableFormat: true
SortIncludes: Never
Loading…
Cancel
Save