chromium/build/nocompile.gni

# 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_clang && !is_nacl
}

if (enable_nocompile_tests) {
  template("nocompile_source_set") {
    action_foreach(target_name) {
      testonly = true

      script = "//tools/nocompile/wrapper.py"
      sources = invoker.sources
      if (defined(invoker.deps)) {
        deps = invoker.deps
      }

      # An action is not a compiler, so configs is empty until it is explicitly
      # set.
      configs = default_compiler_configs
      if (defined(invoker.configs)) {
        configs += invoker.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.
    }
  }
}