1# Copyright 2011 The Chromium Authors 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5# This file is meant to be included into an target to create a unittest that 6# invokes a set of no-compile tests. A no-compile test is a test that asserts 7# a particular construct will not compile. 8# 9# Usage: 10# 11# 1. Create a GN target: 12# 13# import("//build/nocompile.gni") 14# 15# nocompile_source_set("base_nocompile_tests") { 16# sources = [ 17# "functional/one_not_equal_two_nocompile.nc", 18# ] 19# deps = [ 20# ":base" 21# ] 22# } 23# 24# Note that by convention, nocompile tests use the `.nc` extension rather 25# than the standard `.cc` extension: this is because the expectation lines 26# often exceed 80 characters, which would make clang-format unhappy. 27# 28# 2. Add a dep from a related test binary to the nocompile source set: 29# 30# test("base_unittests") { 31# ... 32# deps += [ ":base_nocompile_tests" ] 33# } 34# 35# 3. Populate the .nc file with test cases. Expected compile failures should be 36# annotated with a comment of the form: 37# 38# // expected-error {{<expected error string here>}} 39# 40# For example: 41# 42# void OneDoesNotEqualTwo() { 43# static_assert(1 == 2); // expected-error {{static assertion failed due to requirement '1 == 2'}} 44# } 45# 46# The verification logic is built as part of clang; full documentation is at 47# https://clang.llvm.org/doxygen/classclang_1_1VerifyDiagnosticConsumer.html. 48# 49# Also see: 50# http://dev.chromium.org/developers/testing/no-compile-tests 51# 52import("//build/config/clang/clang.gni") 53if (is_win) { 54 import("//build/toolchain/win/win_toolchain_data.gni") 55} 56 57declare_args() { 58 enable_nocompile_tests = (is_linux || is_chromeos || is_apple || is_win) && 59 is_clang && host_cpu == target_cpu 60 enable_nocompile_tests_new = is_clang && !is_nacl 61} 62 63if (enable_nocompile_tests_new) { 64 template("nocompile_source_set") { 65 action_foreach(target_name) { 66 testonly = true 67 68 script = "//tools/nocompile/wrapper.py" 69 sources = invoker.sources 70 deps = invoker.deps 71 72 # An action is not a compiler, so configs is empty until it is explicitly set. 73 configs = default_compiler_configs 74 75 # Disable the checks that the Chrome style plugin normally enforces to 76 # reduce the amount of boilerplate needed in nocompile tests. 77 configs -= [ "//build/config/clang:find_bad_constructs" ] 78 79 if (is_win) { 80 result_path = 81 "$target_out_dir/$target_name/{{source_name_part}}_placeholder.obj" 82 } else { 83 result_path = 84 "$target_out_dir/$target_name/{{source_name_part}}_placeholder.o" 85 } 86 rebased_obj_path = rebase_path(result_path, root_build_dir) 87 88 depfile = "${result_path}.d" 89 rebased_depfile_path = rebase_path(depfile, root_build_dir) 90 outputs = [ result_path ] 91 92 if (is_win) { 93 if (host_os == "win") { 94 cxx = "clang-cl.exe" 95 } else { 96 cxx = "clang-cl" 97 } 98 } else { 99 cxx = "clang++" 100 } 101 102 args = [] 103 104 if (is_win) { 105 # ninja normally parses /showIncludes output, but the depsformat 106 # variable can only be set in compiler tools, not for custom actions. 107 # Unfortunately, this means the clang wrapper needs to generate the 108 # depfile itself. 109 args += [ "--generate-depfile" ] 110 } 111 112 args += [ 113 rebase_path("$clang_base_path/bin/$cxx", root_build_dir), 114 "{{source}}", 115 rebased_obj_path, 116 rebased_depfile_path, 117 "--", 118 "{{cflags}}", 119 "{{cflags_cc}}", 120 "{{defines}}", 121 "{{include_dirs}}", 122 123 # No need to generate an object file for nocompile tests. 124 "-Xclang", 125 "-fsyntax-only", 126 127 # Enable clang's VerifyDiagnosticConsumer: 128 # https://clang.llvm.org/doxygen/classclang_1_1VerifyDiagnosticConsumer.html 129 "-Xclang", 130 "-verify", 131 132 # But don't require expected-note comments since that is not the 133 # primary point of the nocompile tests. 134 "-Xclang", 135 "-verify-ignore-unexpected=note", 136 137 # Disable the error limit so that nocompile tests do not need to be 138 # arbitrarily split up when they hit the default error limit. 139 "-ferror-limit=0", 140 141 # So funny characters don't show up in error messages. 142 "-fno-color-diagnostics", 143 144 # Always treat warnings as errors. 145 "-Werror", 146 ] 147 148 if (!is_win) { 149 args += [ 150 # On non-Windows platforms, clang can generate the depfile. 151 "-MMD", 152 "-MF", 153 rebased_depfile_path, 154 "-MT", 155 rebased_obj_path, 156 157 # Non-Windows clang uses file extensions to determine how to treat 158 # various inputs, so explicitly tell it to treat all inputs (even 159 # those with weird extensions like .nc) as C++ source files. 160 "-x", 161 "c++", 162 ] 163 } else { 164 # For some reason, the Windows includes are not part of the default 165 # compiler configs. Set it explicitly here, since things like libc++ 166 # depend on the VC runtime. 167 if (target_cpu == "x86") { 168 win_toolchain_data = win_toolchain_data_x86 169 } else if (target_cpu == "x64") { 170 win_toolchain_data = win_toolchain_data_x64 171 } else if (target_cpu == "arm64") { 172 win_toolchain_data = win_toolchain_data_arm64 173 } else { 174 error("Unsupported target_cpu, add it to win_toolchain_data.gni") 175 } 176 args += win_toolchain_data.include_flags_imsvc_list 177 args += [ "/showIncludes:user" ] 178 } 179 180 # Note: for all platforms, the depfile only lists user includes, and not 181 # system includes. If system includes change, the compiler flags are 182 # expected to artificially change in some way to invalidate and force the 183 # nocompile tests to run again. 184 } 185 } 186} 187 188# TODO(https://crbug.com/1480969): this section remains for legacy 189# documentation. However, nocompile tests using these legacy templates are 190# migrated to the new-style tests. Please do not add more old-style tests. 191# 192# To use this, create a GN target with the following form: 193# 194# import("//build/nocompile.gni") 195# nocompile_test("my_module_nc_unittests") { 196# sources = [ 197# 'nc_testset_1.nc', 198# 'nc_testset_2.nc', 199# ] 200# 201# # optional extra include dirs: 202# include_dirs = [ ... ] 203# } 204# 205# The tests are invoked by building the target named in the nocompile_test() 206# macro, for example: 207# 208# ninja -C out/Default my_module_nc_unittests 209# 210# The .nc files are C++ files that contain code we wish to assert will not 211# compile. Each individual test case in the file should be put in its own 212# #if defined(...) section specifying an unique preprocessor symbol beginning 213# with NCTEST which names the test. The set of tests in a file is automatically 214# determined by scanning the file for these #if blocks and no other explicit 215# definition of the symbol is required to register a test. 216# 217# The expected complier error message should be appended with a C++-style 218# comment that has a python list of regular expressions. This will likely be 219# greater than 80-characters. Giving a solid expected output test is important 220# so that random compile failures do not cause the test to pass. 221# 222# Example .nc file: 223# 224# #if defined(NCTEST_NEEDS_SEMICOLON) // [r"expected ',' or ';' at end of input"] 225# 226# int a = 1 227# 228# #elif defined(NCTEST_NEEDS_CAST) // [r"invalid conversion from 'void*' to 'char*'"] 229# 230# void* a = NULL; 231# char* b = a; 232# 233# #endif 234# 235# If we need to disable NCTEST_NEEDS_SEMICOLON, then change the #if to: 236# 237# #if defined(DISABLED_NCTEST_NEEDS_SEMICOLON) 238# ... 239# #elif defined(NCTEST_NEEDS_CAST) 240# ... 241# 242# The lines above are parsed by a regexp so avoid getting creative with the 243# formatting or ifdef logic; it will likely just not work. 244# 245# Implementation notes: 246# The .nc files are actually processed by a python script which executes the 247# compiler and generates a .cc file that is empty on success, or will have a 248# series of #error lines on failure, and a set of trivially passing gunit 249# TEST() functions on success. This allows us to fail at the compile step when 250# something goes wrong, and know during the unittest run that the test was at 251# least processed when things go right. 252 253if (enable_nocompile_tests) { 254 import("//build/config/c++/c++.gni") 255 import("//build/config/sysroot.gni") 256 import("//testing/test.gni") 257 258 if (is_mac) { 259 import("//build/config/mac/mac_sdk.gni") 260 } 261 262 template("nocompile_test") { 263 nocompile_target = target_name + "_run_nocompile" 264 265 action_foreach(nocompile_target) { 266 testonly = true 267 script = "//tools/nocompile/driver.py" 268 sources = invoker.sources 269 deps = invoker.deps 270 if (defined(invoker.public_deps)) { 271 public_deps = invoker.public_deps 272 } 273 274 result_path = "$target_gen_dir/{{source_name_part}}_nc.cc" 275 outputs = [ result_path ] 276 rebased_result_path = rebase_path(result_path, root_build_dir) 277 if (is_win) { 278 if (host_os == "win") { 279 cxx = "clang-cl.exe" 280 nulldevice = "nul" 281 } else { 282 cxx = "clang-cl" 283 284 # Unfortunately, clang-cl always wants to suffix the output file name 285 # with ".obj", and /dev/null.obj is not a valid file. As a bit of a 286 # hack, simply use the path to the generated .cc file, knowing: 287 # - that clang-cl will append ".obj" to the filename, so it will never 288 # clash. 289 # - except when the nocompile test unexpectedly passes, the output 290 # file will never actually be written. 291 nulldevice = rebased_result_path 292 } 293 } else { 294 cxx = "clang++" 295 } 296 297 depfile = "${result_path}.d" 298 299 args = [] 300 if (is_win) { 301 args += [ 302 "--depfile", 303 rebased_result_path + ".d", 304 ] 305 } 306 args += [ 307 rebase_path("$clang_base_path/bin/$cxx", root_build_dir), 308 "4", # number of compilers to invoke in parallel. 309 "{{source}}", 310 rebased_result_path, 311 "--", 312 "-Werror", 313 "-Wfatal-errors", 314 "-Wthread-safety", 315 "-I" + rebase_path("//", root_build_dir), 316 "-I" + rebase_path("//third_party/abseil-cpp/", root_build_dir), 317 "-I" + rebase_path("//buildtools/third_party/libc++/", root_build_dir), 318 "-I" + rebase_path(root_gen_dir, root_build_dir), 319 "-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE", 320 321 # TODO(https://crbug.com/989932): Track build/config/compiler/BUILD.gn 322 "-Wno-implicit-int-float-conversion", 323 ] 324 if (is_win) { 325 # On Windows we fall back to using system headers from a sysroot from 326 # depot_tools. This is negotiated by python scripts and the result is 327 # available in //build/toolchain/win/win_toolchain_data.gni. From there 328 # we get the `include_flags_imsvc` which point to the system headers. 329 if (host_cpu == "x86") { 330 win_toolchain_data = win_toolchain_data_x86 331 } else if (host_cpu == "x64") { 332 win_toolchain_data = win_toolchain_data_x64 333 } else if (host_cpu == "arm64") { 334 win_toolchain_data = win_toolchain_data_arm64 335 } else { 336 error("Unsupported host_cpu, add it to win_toolchain_data.gni") 337 } 338 args += win_toolchain_data.include_flags_imsvc_list 339 340 args += [ 341 "/W4", 342 "-Wno-unused-parameter", 343 "-I" + rebase_path("$libcxx_prefix/include", root_build_dir), 344 "/std:c++20", 345 "/showIncludes", 346 "/Fo" + nulldevice, 347 "/c", 348 "/Tp", 349 ] 350 } else { 351 args += [ 352 "-Wall", 353 "-nostdinc++", 354 "-isystem" + rebase_path("$libcxx_prefix/include", root_build_dir), 355 "-isystem" + rebase_path("$libcxxabi_prefix/include", root_build_dir), 356 "-std=c++20", 357 "-MMD", 358 "-MF", 359 rebased_result_path + ".d", 360 "-MT", 361 rebased_result_path, 362 "-o", 363 "/dev/null", 364 "-c", 365 "-x", 366 "c++", 367 ] 368 } 369 args += [ "{{source}}" ] 370 371 if (is_mac && host_os != "mac") { 372 args += [ 373 "--target=x86_64-apple-macos", 374 "-mmacos-version-min=$mac_deployment_target", 375 ] 376 } 377 378 # Iterate over any extra include dirs and append them to the command line. 379 if (defined(invoker.include_dirs)) { 380 foreach(include_dir, invoker.include_dirs) { 381 args += [ "-I" + rebase_path(include_dir, root_build_dir) ] 382 } 383 } 384 385 if (sysroot != "") { 386 assert(!is_win) 387 sysroot_path = rebase_path(sysroot, root_build_dir) 388 args += [ 389 "--sysroot", 390 sysroot_path, 391 ] 392 } 393 394 if (!is_nacl) { 395 args += [ 396 # TODO(crbug.com/1343975) Evaluate and possibly enable. 397 "-Wno-deprecated-builtins", 398 ] 399 } 400 } 401 402 test(target_name) { 403 deps = invoker.deps + [ ":$nocompile_target" ] 404 sources = get_target_outputs(":$nocompile_target") 405 forward_variables_from(invoker, [ "bundle_deps" ]) 406 } 407 } 408} 409