Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[beman_submodule]
remote=https://github.com/bemanproject/infra.git
commit_hash=7fc85e2e26cb491926ca2744c359c2aa7ea8fd27
commit_hash=a943c4ae58466260f9b98bfd749aa75f5ad7833b
60 changes: 60 additions & 0 deletions cookiecutter/{{cookiecutter.project_name}}/infra/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,66 @@ Some options for the project and target will also be supported:
(default: all packages)
* `<BEMAN_NAME>_INSTALL_CONFIG_FILE_PACKAGE` - a per-project option to enable/disable config file installation (default: `ON` if the project is top-level, `OFF` otherwise). For instance for `beman.something`, the option would be `BEMAN_SOMETHING_INSTALL_CONFIG_FILE_PACKAGE`.

#### `beman_add_xfail_compile_test`

A negative compile test is a translation unit that must *fail* to compile,
and must fail with a specific diagnostic: the `static_assert` message of a
Mandates clause, the message of a `= delete("...")` overload, or the
compiler's "no matching function" when a constraint removes every
candidate. Use `beman_add_xfail_compile_test()` to register one.

```cmake
include(infra/cmake/beman-xfail-compile.cmake)

beman_add_xfail_compile_test(unexpected_array_fail
SOURCE unexpected_array_fail.test.cpp
LINK_LIBRARIES beman::something
)
```

with, near the top of `unexpected_array_fail.test.cpp`:

```cpp
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// NEGATIVE: unexpected<int[]> is ill-formed [expected.un.general]
// EXPECT: "E must not be an array type"
#include <beman/something/unexpected.hpp>

beman::something::unexpected<int[]> u(std::in_place); // must not compile
```

The `EXPECT` line is a CMake regular expression that the compiler output
must match. It lives in the source, next to the ill-formed code it
describes, and is read at configure time. A file may carry several `EXPECT`
lines; they are joined with `|`, so a long alternation covering several
compilers' wordings can be split one per line. Pass `EXPECT <regex>`
to the function instead for a source that cannot carry the comment.

The source becomes an OBJECT library, `<TARGET_PREFIX><name>` (default
prefix `${PROJECT_NAME}.tests.`), excluded from the default build and never
linked. The CTest test `<name>` runs `beman-xfail-compile-driver.cmake`,
which does a nested build of just that target, so the source is compiled
with exactly the toolchain, flags, launcher, and usage requirements the
rest of the project gets. The test passes only when the build fails **and**
its output matches the regex, and reports the two ways of failing
separately:

* *compiled, but was expected to fail* — the constraint or mandate is not
firing;
* *failed to compile, but not for the expected reason* — a typo in the
test, or a compiler whose wording the regex does not yet cover. The build
output is echoed.

Every test carries the label `xfail-compile`, so `ctest -L xfail-compile`
runs just the negative suite and `ctest -LE xfail-compile` skips it, and a
`RESOURCE_LOCK` so `ctest -j` never runs two nested builds in one build
tree at once.

Options: `LINK_LIBRARIES <target>...`, `COMPILE_DEFINITIONS <def>...`,
`TARGET_PREFIX <prefix>`, `LABELS <label>...`. The header comment of
[beman-xfail-compile.cmake](./cmake/beman-xfail-compile.cmake) has the
full interface. `EXPECT` must not contain `;`.

# BuildTelemetry

The cmake modules in this library provide access to CMake instrumentation data in Google Trace format which is visualizable with chrome://tracing and https://ui.perfetto.dev.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# cmake/beman-xfail-compile-driver.cmake -*-CMake-*-
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# Test driver for beman_add_xfail_compile_test(). Run by CTest as
#
# cmake -DBEMAN_XFAIL_BUILD_DIR=<build dir>
# -DBEMAN_XFAIL_TARGET=<object library>
# -DBEMAN_XFAIL_CONFIG=<config or empty>
# -DBEMAN_XFAIL_EXPECT=<regex>
# -P beman-xfail-compile-driver.cmake
#
# It builds just the target and succeeds only when the build fails and its
# output matches the regex. The three outcomes get three distinct reports:
# the source compiled when it should not have; it failed, but not with the
# expected diagnostic; or it failed with the expected diagnostic.

cmake_minimum_required(VERSION 3.30...4.3)

foreach(_var BEMAN_XFAIL_BUILD_DIR BEMAN_XFAIL_TARGET BEMAN_XFAIL_EXPECT)
if(NOT DEFINED ${_var} OR "${${_var}}" STREQUAL "")
message(FATAL_ERROR "beman-xfail-compile-driver: ${_var} is required")
endif()
endforeach()

set(_build_args
--build
"${BEMAN_XFAIL_BUILD_DIR}"
--target
"${BEMAN_XFAIL_TARGET}"
)
if(BEMAN_XFAIL_CONFIG)
list(APPEND _build_args --config "${BEMAN_XFAIL_CONFIG}")
endif()

execute_process(
COMMAND ${CMAKE_COMMAND} ${_build_args}
RESULT_VARIABLE _result
OUTPUT_VARIABLE _output
ERROR_VARIABLE _output
)

if(_result EQUAL 0)
message(
FATAL_ERROR
"beman-xfail-compile-driver: ${BEMAN_XFAIL_TARGET} compiled, but was expected to fail with a diagnostic matching\n"
" ${BEMAN_XFAIL_EXPECT}\n"
"Build output:\n${_output}"
)
endif()

if(NOT _output MATCHES "${BEMAN_XFAIL_EXPECT}")
message(
FATAL_ERROR
"beman-xfail-compile-driver: ${BEMAN_XFAIL_TARGET} failed to compile, but not for the expected reason. No line matches\n"
" ${BEMAN_XFAIL_EXPECT}\n"
"Build output:\n${_output}"
)
endif()

string(
REGEX MATCH "[^\n]*(${BEMAN_XFAIL_EXPECT})[^\n]*"
_matched_line
"${_output}"
)
message(
STATUS
"beman-xfail-compile-driver: ${BEMAN_XFAIL_TARGET} failed as expected: ${_matched_line}"
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# cmake/beman-xfail-compile.cmake -*-CMake-*-
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

include_guard(GLOBAL)

# beman_add_xfail_compile_test
# ============================
#
# Registers a negative compile test: a translation unit that must fail to
# compile, and must fail with a specific diagnostic.
#
# Usage:
# ------
# beman_add_xfail_compile_test(<name>
# SOURCE <file>
# [EXPECT <regex>]
# [LINK_LIBRARIES <target> ...]
# [COMPILE_DEFINITIONS <definition> ...]
# [TARGET_PREFIX <prefix>]
# [LABELS <label> ...]
# )
#
# Arguments:
# ----------
#
# name
# The CTest test name.
#
# SOURCE (required)
# The source file that must not compile.
#
# EXPECT (optional)
# A regular expression matched against the compiler output. The test
# passes only when the output matches, which confirms the failure is the
# intended static_assert, deleted overload, or constraint, and not an
# unrelated build error. Alternations such as
# "use of deleted function|call to deleted" cover compiler phrasings.
#
# When omitted, the regex is read from SOURCE, which must then contain
# one or more lines of the form
#
# // EXPECT: "<regex>"
#
# so the expectation lives next to the ill-formed code it describes.
# Several lines are joined with "|", so a long alternation can be split
# one compiler's wording per line and stay inside a formatter's column
# limit. Configuration re-runs when that file changes. Passing EXPECT
# overrides the comment, for sources that cannot carry one.
#
# LINK_LIBRARIES (optional)
# Targets whose usage requirements the source is compiled with, typically
# the library under test. Nothing is ever linked.
#
# COMPILE_DEFINITIONS (optional)
# Extra preprocessor definitions for the source.
#
# TARGET_PREFIX (optional)
# Prefix of the per-test CMake target. Defaults to "${PROJECT_NAME}.tests."
# so beman.<lib> gets beman.<lib>.tests.<name>.
#
# LABELS (optional)
# CTest labels to attach to the test, in addition to "xfail-compile",
# which every test gets so `ctest -L xfail-compile` runs just the
# negative suite and `ctest -LE xfail-compile` skips it.
#
# Brief
# -----
#
# The source becomes an OBJECT library that is excluded from the default
# build, so an ordinary build never compiles it and nothing links it. The
# test runs beman-xfail-compile-driver.cmake, which performs a nested build
# of just that target, so the source is compiled with exactly the
# toolchain, flags, launcher, and usage requirements the rest of the
# project gets. The driver passes only when that build fails and its
# output matches EXPECT, and it reports the two ways of failing
# separately: the source compiled, or it failed for some other reason.
#
# Every test holds the RESOURCE_LOCK "beman-xfail-compile-build-tree", so
# under `ctest -j` the nested builds never run concurrently in one build
# tree. Other tests still run alongside them.
#
# EXPECT is a CMake regular expression and must not contain ";".

function(beman_add_xfail_compile_test name)
set(oneValueArgs SOURCE EXPECT TARGET_PREFIX)
set(multiValueArgs LINK_LIBRARIES COMPILE_DEFINITIONS LABELS)
cmake_parse_arguments(
BEMAN_XFAIL
""
"${oneValueArgs}"
"${multiValueArgs}"
${ARGN}
)

if(BEMAN_XFAIL_UNPARSED_ARGUMENTS)
message(
FATAL_ERROR
"beman_add_xfail_compile_test(${name}): unexpected arguments '${BEMAN_XFAIL_UNPARSED_ARGUMENTS}'"
)
endif()
if(NOT BEMAN_XFAIL_SOURCE)
message(
FATAL_ERROR
"beman_add_xfail_compile_test(${name}): SOURCE must be specified"
)
endif()
if(NOT DEFINED BEMAN_XFAIL_EXPECT)
file(
REAL_PATH "${BEMAN_XFAIL_SOURCE}"
_source_path
BASE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
)
set_property(
DIRECTORY
APPEND
PROPERTY CMAKE_CONFIGURE_DEPENDS "${_source_path}"
)
file(STRINGS "${_source_path}" _expect_lines REGEX "^// EXPECT: \"")
if(NOT _expect_lines)
message(
FATAL_ERROR
"beman_add_xfail_compile_test(${name}): no '// EXPECT: \"<regex>\"' line in ${_source_path}; add one or pass EXPECT"
)
endif()
set(_alternatives)
foreach(_line IN LISTS _expect_lines)
string(
REGEX REPLACE "^// EXPECT: \"(.*)\"[ \t]*$"
"\\1"
_alternative
"${_line}"
)
list(APPEND _alternatives "${_alternative}")
endforeach()
list(JOIN _alternatives "|" BEMAN_XFAIL_EXPECT)
endif()
if("${BEMAN_XFAIL_EXPECT}" STREQUAL "")
message(
FATAL_ERROR
"beman_add_xfail_compile_test(${name}): EXPECT must not be empty"
)
endif()
string(FIND "${BEMAN_XFAIL_EXPECT}" ";" _semicolon)
if(NOT _semicolon EQUAL -1)
message(
FATAL_ERROR
"beman_add_xfail_compile_test(${name}): EXPECT must not contain ';'"
)
endif()
if(NOT DEFINED BEMAN_XFAIL_TARGET_PREFIX)
set(BEMAN_XFAIL_TARGET_PREFIX "${PROJECT_NAME}.tests.")
endif()

set(_target "${BEMAN_XFAIL_TARGET_PREFIX}${name}")

add_library(${_target} OBJECT)
target_sources(${_target} PRIVATE ${BEMAN_XFAIL_SOURCE})
if(BEMAN_XFAIL_LINK_LIBRARIES)
target_link_libraries(${_target} PRIVATE ${BEMAN_XFAIL_LINK_LIBRARIES})
endif()
if(BEMAN_XFAIL_COMPILE_DEFINITIONS)
target_compile_definitions(
${_target}
PRIVATE ${BEMAN_XFAIL_COMPILE_DEFINITIONS}
)
endif()
set_target_properties(
${_target}
PROPERTIES EXCLUDE_FROM_ALL true EXCLUDE_FROM_DEFAULT_BUILD true
)

add_test(
NAME ${name}
COMMAND
${CMAKE_COMMAND} -DBEMAN_XFAIL_BUILD_DIR=${CMAKE_BINARY_DIR}
-DBEMAN_XFAIL_TARGET=${_target} -DBEMAN_XFAIL_CONFIG=$<CONFIG>
-DBEMAN_XFAIL_EXPECT=${BEMAN_XFAIL_EXPECT} -P
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/beman-xfail-compile-driver.cmake
)
set_tests_properties(
${name}
PROPERTIES
LABELS "xfail-compile;${BEMAN_XFAIL_LABELS}"
RESOURCE_LOCK beman-xfail-compile-build-tree
)
endfunction()
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,8 @@
include(${CMAKE_CURRENT_LIST_DIR}/llvm-toolchain.cmake)

if(NOT CMAKE_CXX_FLAGS MATCHES "-stdlib=libc\\+\\+")
string(APPEND CMAKE_CXX_FLAGS " -stdlib=libc++")
string(
APPEND CMAKE_CXX_FLAGS
" -stdlib=libc++ -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0"
)
endif()
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#!/usr/bin/env bash
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

set -o nounset
set -o errexit
Expand Down
2 changes: 1 addition & 1 deletion infra/.beman_submodule
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[beman_submodule]
remote=https://github.com/bemanproject/infra.git
commit_hash=7fc85e2e26cb491926ca2744c359c2aa7ea8fd27
commit_hash=a943c4ae58466260f9b98bfd749aa75f5ad7833b
Loading
Loading