title | description | ms.date | ms.topic |
---|---|---|---|
vcpkg CMake Style Guide |
The CMake Style Guide for contributing to vcpkg. |
01/10/2024 |
concept-article |
We expect that all CMake scripts that are either:
- In the
scripts/
directory, or - In a
vcpkg-*
port
should follow the guidelines laid out in this document. Existing scripts may not follow these guidelines yet; it is expected that we will continue to update old scripts to fall in line with these guidelines.
These guidelines are intended to create stability in our scripts. We hope that they will make both forwards and backwards compatibility easier.
-
Except for out-parameters, we always use
cmake_parse_arguments()
rather than function parameters or referring to${ARG<N>}
.-
This doesn't necessarily need to be followed for "script-local helper functions"
- In this case, positional parameters should be put in the function
declaration (rather than using
${ARG<N>}
), and should be named according to local rules (i.e.snake_case
). - Exception: positional parameters that are optional should be
given a name via
set(argument_name "${ARG<N>}")
, after checkingARGC
.
- In this case, positional parameters should be put in the function
declaration (rather than using
-
Out-parameters should be the first parameter to a function. Example:
function(format out_var) cmake_parse_arguments(PARSE_ARGV 1 "arg" ...) # ... set(buffer "output") set("${out_var}" "${buffer}" PARENT_SCOPE) endfunction()
-
-
There are no unparsed or unused arguments. Always check for
ARGN
orarg_UNPARSED_ARGUMENTS
.FATAL_ERROR
when possible,WARNING
if necessary for backwards compatibility. -
All
cmake_parse_arguments
must usePARSE_ARGV
. -
All
foreach
loops must useIN LISTS
,IN ITEMS
, orRANGE
. -
The variables
${ARGV}
and${ARGN}
are unreferenced, except in helpful messages to the user.- (i.e.,
message(FATAL_ERROR "blah was passed extra arguments: ${ARGN}")
)
- (i.e.,
-
We always use functions, not macros or top level code.
- Exception: "script-local helper macros". It is sometimes helpful to define a small macro. This should be done sparingly, and functions should be preferred.
- Exception:
vcpkg.cmake
'sfind_package
.
-
Scripts in the scripts tree should not be expected to need observable changes as part of normal operation.
- Example violation:
vcpkg_acquire_msys()
has hard-coded packages and versions that need updating over time due to the MSYS project dropping old packages. - Example exception:
vcpkg_from_sourceforge()
has a list of mirrors which needs maintenance, but does not have an observable behavior impact on the callers.
- Example violation:
-
Rules for quoting: there are three kinds of arguments in CMake - unquoted (
foo(BAR)
), quoted (foo("BAR")
), and bracketed (foo([[BAR]])
). Follow these rules to quote correctly:-
If an argument contains a variable expansion
${...}
, it must be quoted.-
Exception: a "splat" variable expansion, when one variable will be passed to a function as multiple arguments. In this case, the argument should simply be
${foo}
:vcpkg_list(SET working_directory) if(DEFINED "arg_WORKING_DIRECTORY") vcpkg_list(SET working_directory WORKING_DIRECTORY "${arg_WORKING_DIRECTORY}") endif() # calls do_the_thing() if NOT DEFINED arg_WORKING_DIRECTORY, # else calls do_the_thing(WORKING_DIRECTORY "${arg_WORKING_DIRECTORY}") do_the_thing(${working_directory})
-
-
Otherwise, if the argument contains any escape sequences that are not
\\
,\"
, or\$
, that argument must be a quoted argument.- For example:
"foo\nbar"
must be quoted.
- For example:
-
Otherwise, if the argument contains a
\
, a"
, or a$
, that argument should be bracketed.-
Example:
set(x [[foo\bar]]) set(y [=[foo([[bar\baz]])]=])
-
-
Otherwise, if the argument contains characters that are not alphanumeric or
_
, that argument should be quoted. -
Otherwise, the argument should be unquoted.
-
Exception: arguments to
if()
of type<variable|string>
should always be quoted:-
Both arguments to the comparison operators -
EQUAL
,STREQUAL
,VERSION_LESS
, etc. -
The first argument to
MATCHES
andIN_LIST
-
Example:
if("${FOO}" STREQUAL "BAR") # ... if("${BAZ}" EQUAL "0") # ... if("FOO" IN_LIST list_variable) # ... if("${bar}" MATCHES [[a[bcd]+\.[bcd]+]]) # ...
-
For single expressions and for other types of predicates that do not take
<variable|string>
, use the normal rules.
-
-
-
There are no "pointer" or "in-out" parameters (where a user passes a variable name rather than the contents), except for simple out-parameters.
-
Variables are not assumed to be empty. If the variable is intended to be used locally, it must be explicitly initialized to empty with
set(foo "")
if it is a string variable, andvcpkg_list(SET foo)
if it is a list variable. -
set(var)
should not be used. Useunset(var)
to unset a variable,set(var "")
to set it to the empty string, andvcpkg_list(SET var)
to set it to the empty list. Note: the empty string and the empty list are the same value; this is a notational difference rather than a difference in result -
All variables expected to be inherited from the parent scope across an API boundary (i.e. not a file-local function) should be documented. All variables mentioned in Triplet files are considered documented.
-
Out parameters are only set in
PARENT_SCOPE
and are never read. See also the helperz_vcpkg_forward_output_variable()
to forward out parameters through a function scope. -
CACHE
variables are used only for global variables which are shared internally among strongly coupled functions and for internal state within a single function to avoid duplicating work. These should be used extremely sparingly and should use theZ_VCPKG_
prefix to avoid colliding with any local variables that would be defined by any other code.- Examples:
vcpkg_cmake_configure
'sZ_VCPKG_CMAKE_GENERATOR
z_vcpkg_get_cmake_vars
'sZ_VCPKG_GET_CMAKE_VARS_FILE
- Examples:
-
include()
s are only allowed inports.cmake
orvcpkg-port-config.cmake
. -
foreach(RANGE)
's arguments must always be natural numbers, and<start>
must always be less than or equal to<stop>
.-
This must be checked by something like:
if("${start}" LESS_EQUAL "${end}") foreach(RANGE "${start}" "${end}") ... endforeach() endif()
-
-
All port-based scripts must use
include_guard(GLOBAL)
to avoid being included multiple times.
- All CMake scripts, except for
vcpkg.cmake
, may assume the version of CMake that is present in thecmake_minimum_required
ofports.cmake
.- This
cmake_minimum_required
should be bumped every time a new version of CMake is added tovcpkgTools.xml
, as should thecmake_minimum_required
in all of the helperCMakeLists.txt
files.
- This
vcpkg.cmake
must assume a version of CMake back to 3.7.2 in general- Specific functions and options may assume a greater CMake version; if they do, make sure to comment that function or option with the required CMake version.
- Never remove arguments in non-internal functions; if they should no longer do anything, just take them as normal and warn on use.
- Never add a new mandatory argument.
-
cmake_parse_arguments
: set prefix to"arg"
-
Local variables are named with
snake_case
-
Internal global variable names are prefixed with
Z_VCPKG_
. -
External experimental global variable names are prefixed with
X_VCPKG_
. -
Internal functions are prefixed with
z_vcpkg_
- Functions which are internal to a single function (i.e., helper functions)
are named
[z_]<func>_<name>
, where<func>
is the name of the function they are a helper to, and<name>
is what the helper function does.z_
should be added to the front if<func>
doesn't have az_
, but don't name a helper functionz_z_foo_bar
.
- Functions which are internal to a single function (i.e., helper functions)
are named
-
Public global variables are named
VCPKG_
.