Try Me

In the TCPM try me directory we provide a ready-made template so you can see how TCPM works for yourself. Simply download this file into a new directory, cd into the directory, and do:

tcpm

What just happened?

You should now have a, relatively large, CMakePresets.json file in this directory. It was generated from CMakePresetsVendorTemplate.json which is the default name for TCPM templates. Let’s take a look at this file:

  1{
  2    "version": 9,
  3    "cmakeMinimumRequired": {
  4        "major": 3,
  5        "minor": 30,
  6        "patch": 0
  7    },
  8    "configurePresets": [
  9        {
 10            "name": "configure-common",
 11            "hidden": true,
 12            "generator": "Ninja Multi-Config",
 13            "binaryDir": "${sourceDir}/build",
 14            "warnings": {
 15                "deprecated": true,
 16                "uninitialized": true
 17            },
 18            "cacheVariables": {
 19                "CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
 20                "CMAKE_PREFIX_PATH": "${sourceParentDir}",
 21                "CMAKE_CONFIGURATION_TYPES": "Release;RelSize;Debug;DebugAsan",
 22                "CMAKE_CROSS_CONFIGS": "all",
 23                "CMAKE_DEFAULT_BUILD_TYPE": "Debug",
 24                "CMAKE_DEFAULT_CONFIGS": "Debug"
 25            }
 26        }
 27    ],
 28    "vendor": {
 29        "tcpm": {
 30            "version": 1,
 31            "onload": [
 32                "$('#preset-group-build parameters toolchain').json($('#preset-group-config parameters toolchain').json()).exp()",
 33                "$('#preset-group-build parameters standard').json($('#preset-group-config parameters standard').json()).exp()",
 34                "$('#preset-group-build parameters configuration').json($('#configure-common cacheVariables CMAKE_CONFIGURATION_TYPES').text().split(';')).exp()",
 35                "$('#preset-group-workflow shape-parameters configuration').json($('#configure-common cacheVariables CMAKE_CONFIGURATION_TYPES').text().split(';')).exp()",
 36                "$('#preset-group-workflow parameters toolchain').json($('#preset-group-config parameters toolchain').json()).exp()",
 37                "$('#preset-group-workflow parameters standard').json($('#preset-group-config parameters standard').json()).exp()"
 38            ],
 39            "preset-groups": {
 40                "configure": {
 41                    "name": "preset-group-config",
 42                    "common": [
 43                        "configure-common"
 44                    ],
 45                    "parameters": {
 46                        "toolchain": [
 47                            "gcc-native",
 48                            "gcc-native-32",
 49                            "clang-native"
 50                        ],
 51                        "standard": [
 52                            "cpp-14",
 53                            "cpp-17",
 54                            "cpp-20"
 55                        ]
 56                    },
 57                    "shape": {
 58                        "toolchain": {
 59                            "toolchainFile": "${{sourceParentDir}}/.devcontainer/cmake/toolchains/{parameter}.cmake",
 60                            "cacheVariables": {
 61                                "MY_TARGET_PLATFORM": "{pq}(this).if('{name}' $= 'gcc-native-32', 'm32', 'native')"
 62                            }
 63                        },
 64                        "standard": {
 65                            "cacheVariables": {
 66                                "CMAKE_CXX_STANDARD": "{pq}(this).literal('{parameter}').split('{sep}').get(1)"
 67                            }
 68                        }
 69                    }
 70                },
 71                "build": {
 72                    "name": "preset-group-build",
 73                    "parameters": {
 74                        "configuration": [],
 75                        "toolchain": [],
 76                        "standard": []
 77                    },
 78                    "shape": {
 79                        "configuration": {
 80                            "configurePreset": "{pq}(this).literal('{name}').replace('{prefix}{sep}{parameter}', '{groups[configure][prefix]}')",
 81                            "configuration": "{parameter}",
 82                            "targets": [
 83                                "build",
 84                                "build_examples",
 85                                "build_unittests",
 86                                "build_compile_tests",
 87                                "docs",
 88                                "lint",
 89                                "release"
 90                            ]
 91                        }
 92                    }
 93                },
 94                "workflow": {
 95                    "name": "preset-group-workflow",
 96                    "parameters": {
 97                        "toolchain": [],
 98                        "standard": []
 99                    },
100                    "shape-parameters": {
101                        "configuration": []
102                    },
103                    "shape": {
104                        "toolchain": {
105                            "displayName": "{pq}(this).literal('{name}').replace('{groups[workflow][prefix]}{sep}','').replace('{sep}', ' ')",
106                            "description": "autogenerated workflow",
107                            "steps": [
108                                {
109                                    "type": "configure",
110                                    "name": "{pq}(this).literal('{name}').replace('{groups[workflow][prefix]}{sep}', '{groups[configure][prefix]}{sep}')"
111                                }
112                            ]
113                        },
114                        "configuration": {
115                            "steps": [
116                                {
117                                    "type": "build",
118                                    "name": "{pq}(this).literal('{name}').replace('{groups[workflow][prefix]}{sep}', '{groups[build][prefix]}{sep}{parameter}{sep}')"
119                                }
120                            ]
121                        }
122                    }
123                }
124            }
125        }
126    }
127}

The first thing you might notice is this is a valid CMakePresets.json file with a single "configurePresets" entry, "configure-common". The template part of this file is found in the top-level "vendor" section starting with the tcpm object on line 29. In this object the preset-groups object should look a bit familiar having objects named “configure”, “build”, and “workflow”. Each of these corresponds to preset groups by simple textual concatenation of “Presets” to the end of the identifier (e.g. “configure” => “configurePresets”).

On line 41, the "configure" object contains all of the information used to generate "configurePresets". Let’s go over each item in this object:

Key

Req’d?

Type

Description

"name"

no

string

A name for this object to allow direct addressing in pQuery statements as $('#{identifier}').

"common"

no

array[string]

A list of "configurePresets" entries that all generated configure presets will inherit from. This field is only used for for "configure" presets group.

"parameters"

yes

object[string, array[string]]

This is where the magic happens. Parameters define each dimension of a matrix and it is the cartesian product of each of these parameter lists that TCPM uses to generate new presets.

"shape-parameters"

no

object[string, array[string]]

The same as parameters except these are not used to create presets but, for any presets created, are used to instatiate parameterized shapes for each preset.

"shape"

no

object[string, object]

Shapes act like templates for parameters and shape-parameters. Each shape is used when a given parameter is part of a new preset definition to append additional data to the preset object. "cacheVariables", for example, can be defined for new presets using shapes whereas worflow steps can be defined using shape-parameterized shapes.

In our example we generated every posible configuration needed to build a project for three different toolchains using three different C++ standards. Let’s suppose we want to run these build presets using Github Actions. We’d find that this system supports a similar syntax for defining a matrix of build jobs:

jobs:
  example_matrix:
    strategy:
      matrix:
        toolchain: ["gcc-native", "gcc-native-32", "clang-native"]
        standard:  ["cpp-14", "cpp-17", "cpp-20"]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: cmake --workflow --preset workflow-${{ matrix.toolchain }}-${{ matrix.standard }}

Let’s say we don’t want to build "cpp-20" using the "gcc-native-32" toolchain. Github actions allows pruning the result set using exclude. For example:

jobs:
  example_matrix:
    strategy:
      matrix:
        toolchain: ["gcc-native", "gcc-native-32", "clang-native"]
        standard:  ["cpp-14", "cpp-17", "cpp-20"]
      exclude:
        - toolchain: gcc-native-32
          standard: cpp-20
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: cmake --workflow --preset workflow-${{ matrix.toolchain }}-${{ matrix.standard }}

TCPM supports a similar syntax in JSON form to prevent generation of this workflow:

"exclude": [
    {
        "toolchain": "gcc-native-32",
        "standard": "cpp-20"
    }
],

Try adding the above exclude to the CMakePresetsVendorTemplate.json under ["vendor"]["tcpm"]["preset-groups"]["workflow"]. Now do tcpm -f (-f to force overwrite of the existing CMakePresets.json file) and you’ll have a slightly smaller presets file that does not provide a workflow for this combination of parameters.