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.
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.
11 # 1. Create a GN target:
13 # import("//build/nocompile.gni")
15 # nocompile_source_set("base_nocompile") {
17 # "functional/one_not_equal_two_nocompile.nc",
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.
28 # 2. Add a dep from a related test binary to the nocompile source set:
30 # test("base_unittests") {
32 # deps += [ ":base_nocompile_tests" ]
35 # 3. Populate the .nc file with test cases. Expected compile failures should be
36 # annotated with a comment of the form:
38 # // expected-error {{<expected error string here>}}
42 # void OneDoesNotEqualTwo() {
43 # static_assert(1 == 2); // expected-error {{static assertion failed due to requirement '1 == 2'}}
46 # The verification logic is built as part of clang; full documentation is at
47 # https://clang.llvm.org/doxygen/classclang_1_1VerifyDiagnosticConsumer.html.
50 # http://dev.chromium.org/developers/testing/no-compile-tests
52 import("//build/config/clang/clang.gni")
54 import("//build/toolchain/win/win_toolchain_data.gni")
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 && !is_tizen
63 if (enable_nocompile_tests_new) {
64 template("nocompile_source_set") {
65 action_foreach(target_name) {
68 script = "//tools/nocompile/wrapper.py"
69 sources = invoker.sources
72 # An action is not a compiler, so configs is empty until it is explicitly set.
73 configs = default_compiler_configs
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" ]
81 "$target_out_dir/$target_name/{{source_name_part}}_placeholder.obj"
84 "$target_out_dir/$target_name/{{source_name_part}}_placeholder.o"
86 rebased_obj_path = rebase_path(result_path, root_build_dir)
88 depfile = "${result_path}.d"
89 rebased_depfile_path = rebase_path(depfile, root_build_dir)
90 outputs = [ result_path ]
93 if (host_os == "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
109 args += [ "--generate-depfile" ]
113 rebase_path("$clang_base_path/bin/$cxx", root_build_dir),
116 rebased_depfile_path,
123 # No need to generate an object file for nocompile tests.
127 # Enable clang's VerifyDiagnosticConsumer:
128 # https://clang.llvm.org/doxygen/classclang_1_1VerifyDiagnosticConsumer.html
132 # But don't require expected-note comments since that is not the
133 # primary point of the nocompile tests.
135 "-verify-ignore-unexpected=note",
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.
141 # So funny characters don't show up in error messages.
142 "-fno-color-diagnostics",
144 # Always treat warnings as errors.
150 # On non-Windows platforms, clang can generate the depfile.
153 rebased_depfile_path,
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.
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
174 error("Unsupported target_cpu, add it to win_toolchain_data.gni")
176 args += win_toolchain_data.include_flags_imsvc_list
177 args += [ "/showIncludes:user" ]
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.
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.
192 # To use this, create a GN target with the following form:
194 # import("//build/nocompile.gni")
195 # nocompile_test("my_module_nc_unittests") {
201 # # optional extra include dirs:
202 # include_dirs = [ ... ]
205 # The tests are invoked by building the target named in the nocompile_test()
206 # macro, for example:
208 # ninja -C out/Default my_module_nc_unittests
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.
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.
224 # #if defined(NCTEST_NEEDS_SEMICOLON) // [r"expected ',' or ';' at end of input"]
228 # #elif defined(NCTEST_NEEDS_CAST) // [r"invalid conversion from 'void*' to 'char*'"]
235 # If we need to disable NCTEST_NEEDS_SEMICOLON, then change the #if to:
237 # #if defined(DISABLED_NCTEST_NEEDS_SEMICOLON)
239 # #elif defined(NCTEST_NEEDS_CAST)
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.
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.
253 if (enable_nocompile_tests) {
254 import("//build/config/c++/c++.gni")
255 import("//build/config/sysroot.gni")
256 import("//testing/test.gni")
259 import("//build/config/mac/mac_sdk.gni")
262 template("nocompile_test") {
263 nocompile_target = target_name + "_run_nocompile"
265 action_foreach(nocompile_target) {
267 script = "//tools/nocompile/driver.py"
268 sources = invoker.sources
270 if (defined(invoker.public_deps)) {
271 public_deps = invoker.public_deps
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)
278 if (host_os == "win") {
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
289 # - except when the nocompile test unexpectedly passes, the output
290 # file will never actually be written.
291 nulldevice = rebased_result_path
297 depfile = "${result_path}.d"
303 rebased_result_path + ".d",
307 rebase_path("$clang_base_path/bin/$cxx", root_build_dir),
308 "4", # number of compilers to invoke in parallel.
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),
320 # TODO(https://crbug.com/989932): Track build/config/compiler/BUILD.gn
321 "-Wno-implicit-int-float-conversion",
324 # On Windows we fall back to using system headers from a sysroot from
325 # depot_tools. This is negotiated by python scripts and the result is
326 # available in //build/toolchain/win/win_toolchain_data.gni. From there
327 # we get the `include_flags_imsvc` which point to the system headers.
328 if (host_cpu == "x86") {
329 win_toolchain_data = win_toolchain_data_x86
330 } else if (host_cpu == "x64") {
331 win_toolchain_data = win_toolchain_data_x64
332 } else if (host_cpu == "arm64") {
333 win_toolchain_data = win_toolchain_data_arm64
335 error("Unsupported host_cpu, add it to win_toolchain_data.gni")
337 args += win_toolchain_data.include_flags_imsvc_list
341 "-Wno-unused-parameter",
342 "-I" + rebase_path("$libcxx_prefix/include", root_build_dir),
353 "-isystem" + rebase_path("$libcxx_prefix/include", root_build_dir),
354 "-isystem" + rebase_path("$libcxxabi_prefix/include", root_build_dir),
358 rebased_result_path + ".d",
368 args += [ "{{source}}" ]
370 if (is_mac && host_os != "mac") {
372 "--target=x86_64-apple-macos",
373 "-mmacos-version-min=$mac_deployment_target",
377 # Iterate over any extra include dirs and append them to the command line.
378 if (defined(invoker.include_dirs)) {
379 foreach(include_dir, invoker.include_dirs) {
380 args += [ "-I" + rebase_path(include_dir, root_build_dir) ]
386 sysroot_path = rebase_path(sysroot, root_build_dir)
395 # TODO(crbug.com/1343975) Evaluate and possibly enable.
396 "-Wno-deprecated-builtins",
402 deps = invoker.deps + [ ":$nocompile_target" ]
403 sources = get_target_outputs(":$nocompile_target")
404 forward_variables_from(invoker, [ "bundle_deps" ])