| # Copyright 2011 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| # This file is meant to be included into an target to create a unittest that |
| # invokes a set of no-compile tests. A no-compile test is a test that asserts |
| # a particular construct will not compile. |
| # |
| # Usage: |
| # |
| # 1. Create a GN target: |
| # |
| # import("//build/nocompile.gni") |
| # |
| # nocompile_source_set("base_nocompile_tests") { |
| # sources = [ |
| # "functional/one_not_equal_two_nocompile.nc", |
| # ] |
| # deps = [ |
| # ":base" |
| # ] |
| # } |
| # |
| # Note that by convention, nocompile tests use the `.nc` extension rather |
| # than the standard `.cc` extension: this is because the expectation lines |
| # often exceed 80 characters, which would make clang-format unhappy. |
| # |
| # 2. Add a dep from a related test binary to the nocompile source set: |
| # |
| # test("base_unittests") { |
| # ... |
| # deps += [ ":base_nocompile_tests" ] |
| # } |
| # |
| # 3. Populate the .nc file with test cases. Expected compile failures should be |
| # annotated with a comment of the form: |
| # |
| # // expected-error {{<expected error string here>}} |
| # |
| # For example: |
| # |
| # void OneDoesNotEqualTwo() { |
| # static_assert(1 == 2); // expected-error {{static assertion failed due to requirement '1 == 2'}} |
| # } |
| # |
| # The verification logic is built as part of clang; full documentation is at |
| # https://clang.llvm.org/doxygen/classclang_1_1VerifyDiagnosticConsumer.html. |
| # |
| # Also see: |
| # http://dev.chromium.org/developers/testing/no-compile-tests |
| # |
| import("//build/config/clang/clang.gni") |
| if (is_win) { |
| import("//build/toolchain/win/win_toolchain_data.gni") |
| } |
| |
| declare_args() { |
| enable_nocompile_tests = (is_linux || is_chromeos || is_apple || is_win) && |
| is_clang && host_cpu == target_cpu |
| enable_nocompile_tests_new = is_clang && !is_nacl |
| } |
| |
| if (enable_nocompile_tests_new) { |
| template("nocompile_source_set") { |
| action_foreach(target_name) { |
| testonly = true |
| |
| script = "//tools/nocompile/wrapper.py" |
| sources = invoker.sources |
| deps = invoker.deps |
| |
| # An action is not a compiler, so configs is empty until it is explicitly set. |
| configs = default_compiler_configs |
| |
| # Disable the checks that the Chrome style plugin normally enforces to |
| # reduce the amount of boilerplate needed in nocompile tests. |
| configs -= [ "//build/config/clang:find_bad_constructs" ] |
| |
| if (is_win) { |
| result_path = |
| "$target_out_dir/$target_name/{{source_name_part}}_placeholder.obj" |
| } else { |
| result_path = |
| "$target_out_dir/$target_name/{{source_name_part}}_placeholder.o" |
| } |
| rebased_obj_path = rebase_path(result_path, root_build_dir) |
| |
| depfile = "${result_path}.d" |
| rebased_depfile_path = rebase_path(depfile, root_build_dir) |
| outputs = [ result_path ] |
| |
| if (is_win) { |
| if (host_os == "win") { |
| cxx = "clang-cl.exe" |
| } else { |
| cxx = "clang-cl" |
| } |
| } else { |
| cxx = "clang++" |
| } |
| |
| args = [] |
| |
| if (is_win) { |
| # ninja normally parses /showIncludes output, but the depsformat |
| # variable can only be set in compiler tools, not for custom actions. |
| # Unfortunately, this means the clang wrapper needs to generate the |
| # depfile itself. |
| args += [ "--generate-depfile" ] |
| } |
| |
| args += [ |
| rebase_path("$clang_base_path/bin/$cxx", root_build_dir), |
| "{{source}}", |
| rebased_obj_path, |
| rebased_depfile_path, |
| "--", |
| "{{cflags}}", |
| "{{cflags_cc}}", |
| "{{defines}}", |
| "{{include_dirs}}", |
| |
| # No need to generate an object file for nocompile tests. |
| "-Xclang", |
| "-fsyntax-only", |
| |
| # Enable clang's VerifyDiagnosticConsumer: |
| # https://clang.llvm.org/doxygen/classclang_1_1VerifyDiagnosticConsumer.html |
| "-Xclang", |
| "-verify", |
| |
| # But don't require expected-note comments since that is not the |
| # primary point of the nocompile tests. |
| "-Xclang", |
| "-verify-ignore-unexpected=note", |
| |
| # Disable the error limit so that nocompile tests do not need to be |
| # arbitrarily split up when they hit the default error limit. |
| "-ferror-limit=0", |
| |
| # So funny characters don't show up in error messages. |
| "-fno-color-diagnostics", |
| |
| # Always treat warnings as errors. |
| "-Werror", |
| ] |
| |
| if (!is_win) { |
| args += [ |
| # On non-Windows platforms, clang can generate the depfile. |
| "-MMD", |
| "-MF", |
| rebased_depfile_path, |
| "-MT", |
| rebased_obj_path, |
| |
| # Non-Windows clang uses file extensions to determine how to treat |
| # various inputs, so explicitly tell it to treat all inputs (even |
| # those with weird extensions like .nc) as C++ source files. |
| "-x", |
| "c++", |
| ] |
| } else { |
| # For some reason, the Windows includes are not part of the default |
| # compiler configs. Set it explicitly here, since things like libc++ |
| # depend on the VC runtime. |
| if (target_cpu == "x86") { |
| win_toolchain_data = win_toolchain_data_x86 |
| } else if (target_cpu == "x64") { |
| win_toolchain_data = win_toolchain_data_x64 |
| } else if (target_cpu == "arm64") { |
| win_toolchain_data = win_toolchain_data_arm64 |
| } else { |
| error("Unsupported target_cpu, add it to win_toolchain_data.gni") |
| } |
| args += win_toolchain_data.include_flags_imsvc_list |
| args += [ "/showIncludes:user" ] |
| } |
| |
| # Note: for all platforms, the depfile only lists user includes, and not |
| # system includes. If system includes change, the compiler flags are |
| # expected to artificially change in some way to invalidate and force the |
| # nocompile tests to run again. |
| } |
| } |
| } |
| |
| # TODO(https://crbug.com/1480969): this section remains for legacy |
| # documentation. However, nocompile tests using these legacy templates are |
| # migrated to the new-style tests. Please do not add more old-style tests. |
| # |
| # To use this, create a GN target with the following form: |
| # |
| # import("//build/nocompile.gni") |
| # nocompile_test("my_module_nc_unittests") { |
| # sources = [ |
| # 'nc_testset_1.nc', |
| # 'nc_testset_2.nc', |
| # ] |
| # |
| # # optional extra include dirs: |
| # include_dirs = [ ... ] |
| # } |
| # |
| # The tests are invoked by building the target named in the nocompile_test() |
| # macro, for example: |
| # |
| # ninja -C out/Default my_module_nc_unittests |
| # |
| # The .nc files are C++ files that contain code we wish to assert will not |
| # compile. Each individual test case in the file should be put in its own |
| # #if defined(...) section specifying an unique preprocessor symbol beginning |
| # with NCTEST which names the test. The set of tests in a file is automatically |
| # determined by scanning the file for these #if blocks and no other explicit |
| # definition of the symbol is required to register a test. |
| # |
| # The expected complier error message should be appended with a C++-style |
| # comment that has a python list of regular expressions. This will likely be |
| # greater than 80-characters. Giving a solid expected output test is important |
| # so that random compile failures do not cause the test to pass. |
| # |
| # Example .nc file: |
| # |
| # #if defined(NCTEST_NEEDS_SEMICOLON) // [r"expected ',' or ';' at end of input"] |
| # |
| # int a = 1 |
| # |
| # #elif defined(NCTEST_NEEDS_CAST) // [r"invalid conversion from 'void*' to 'char*'"] |
| # |
| # void* a = NULL; |
| # char* b = a; |
| # |
| # #endif |
| # |
| # If we need to disable NCTEST_NEEDS_SEMICOLON, then change the #if to: |
| # |
| # #if defined(DISABLED_NCTEST_NEEDS_SEMICOLON) |
| # ... |
| # #elif defined(NCTEST_NEEDS_CAST) |
| # ... |
| # |
| # The lines above are parsed by a regexp so avoid getting creative with the |
| # formatting or ifdef logic; it will likely just not work. |
| # |
| # Implementation notes: |
| # The .nc files are actually processed by a python script which executes the |
| # compiler and generates a .cc file that is empty on success, or will have a |
| # series of #error lines on failure, and a set of trivially passing gunit |
| # TEST() functions on success. This allows us to fail at the compile step when |
| # something goes wrong, and know during the unittest run that the test was at |
| # least processed when things go right. |
| |
| if (enable_nocompile_tests) { |
| import("//build/config/c++/c++.gni") |
| import("//build/config/sysroot.gni") |
| import("//testing/test.gni") |
| |
| if (is_mac) { |
| import("//build/config/mac/mac_sdk.gni") |
| } |
| |
| template("nocompile_test") { |
| nocompile_target = target_name + "_run_nocompile" |
| |
| action_foreach(nocompile_target) { |
| testonly = true |
| script = "//tools/nocompile/driver.py" |
| sources = invoker.sources |
| deps = invoker.deps |
| if (defined(invoker.public_deps)) { |
| public_deps = invoker.public_deps |
| } |
| |
| result_path = "$target_gen_dir/{{source_name_part}}_nc.cc" |
| outputs = [ result_path ] |
| rebased_result_path = rebase_path(result_path, root_build_dir) |
| if (is_win) { |
| if (host_os == "win") { |
| cxx = "clang-cl.exe" |
| nulldevice = "nul" |
| } else { |
| cxx = "clang-cl" |
| |
| # Unfortunately, clang-cl always wants to suffix the output file name |
| # with ".obj", and /dev/null.obj is not a valid file. As a bit of a |
| # hack, simply use the path to the generated .cc file, knowing: |
| # - that clang-cl will append ".obj" to the filename, so it will never |
| # clash. |
| # - except when the nocompile test unexpectedly passes, the output |
| # file will never actually be written. |
| nulldevice = rebased_result_path |
| } |
| } else { |
| cxx = "clang++" |
| } |
| |
| depfile = "${result_path}.d" |
| |
| args = [] |
| if (is_win) { |
| args += [ |
| "--depfile", |
| rebased_result_path + ".d", |
| ] |
| } |
| args += [ |
| rebase_path("$clang_base_path/bin/$cxx", root_build_dir), |
| "4", # number of compilers to invoke in parallel. |
| "{{source}}", |
| rebased_result_path, |
| "--", |
| "-Werror", |
| "-Wfatal-errors", |
| "-Wthread-safety", |
| "-I" + rebase_path("//", root_build_dir), |
| "-I" + rebase_path("//third_party/abseil-cpp/", root_build_dir), |
| "-I" + rebase_path("//buildtools/third_party/libc++/", root_build_dir), |
| "-I" + rebase_path(root_gen_dir, root_build_dir), |
| "-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE", |
| |
| # TODO(https://crbug.com/989932): Track build/config/compiler/BUILD.gn |
| "-Wno-implicit-int-float-conversion", |
| ] |
| if (is_win) { |
| # On Windows we fall back to using system headers from a sysroot from |
| # depot_tools. This is negotiated by python scripts and the result is |
| # available in //build/toolchain/win/win_toolchain_data.gni. From there |
| # we get the `include_flags_imsvc` which point to the system headers. |
| if (host_cpu == "x86") { |
| win_toolchain_data = win_toolchain_data_x86 |
| } else if (host_cpu == "x64") { |
| win_toolchain_data = win_toolchain_data_x64 |
| } else if (host_cpu == "arm64") { |
| win_toolchain_data = win_toolchain_data_arm64 |
| } else { |
| error("Unsupported host_cpu, add it to win_toolchain_data.gni") |
| } |
| args += win_toolchain_data.include_flags_imsvc_list |
| |
| args += [ |
| "/W4", |
| "-Wno-unused-parameter", |
| "-I" + rebase_path("$libcxx_prefix/include", root_build_dir), |
| "/std:c++20", |
| "/showIncludes", |
| "/Fo" + nulldevice, |
| "/c", |
| "/Tp", |
| ] |
| } else { |
| args += [ |
| "-Wall", |
| "-nostdinc++", |
| "-isystem" + rebase_path("$libcxx_prefix/include", root_build_dir), |
| "-isystem" + rebase_path("$libcxxabi_prefix/include", root_build_dir), |
| "-std=c++20", |
| "-MMD", |
| "-MF", |
| rebased_result_path + ".d", |
| "-MT", |
| rebased_result_path, |
| "-o", |
| "/dev/null", |
| "-c", |
| "-x", |
| "c++", |
| ] |
| } |
| args += [ "{{source}}" ] |
| |
| if (is_mac && host_os != "mac") { |
| args += [ |
| "--target=x86_64-apple-macos", |
| "-mmacos-version-min=$mac_deployment_target", |
| ] |
| } |
| |
| # Iterate over any extra include dirs and append them to the command line. |
| if (defined(invoker.include_dirs)) { |
| foreach(include_dir, invoker.include_dirs) { |
| args += [ "-I" + rebase_path(include_dir, root_build_dir) ] |
| } |
| } |
| |
| if (sysroot != "") { |
| assert(!is_win) |
| sysroot_path = rebase_path(sysroot, root_build_dir) |
| args += [ |
| "--sysroot", |
| sysroot_path, |
| ] |
| } |
| |
| if (!is_nacl) { |
| args += [ |
| # TODO(crbug.com/1343975) Evaluate and possibly enable. |
| "-Wno-deprecated-builtins", |
| ] |
| } |
| } |
| |
| test(target_name) { |
| deps = invoker.deps + [ ":$nocompile_target" ] |
| sources = get_target_outputs(":$nocompile_target") |
| forward_variables_from(invoker, [ "bundle_deps" ]) |
| } |
| } |
| } |