-
Notifications
You must be signed in to change notification settings - Fork 4.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add layering check support to cc_configure on Linux with Clang
This PR teaches the C++ toolchain autoconfiguration (`cc_configure`) about `layering_check`. Layering check is a feature of C++ rules in concert with clang that will fail the compilation action when a transitive header was depended on directly. The fix is to add a direct dependency on the target providing the header. The result is more explicit dependency graph where one can be sure that all targets that depend on a header depend on it directly. Layering check also validates that private headers are not depended on directly. To make your codebase satisfy the layering check incrementally, I suggest to enable layering check by default (e.g. by putting `build --features=layering_check` into your `.bazelrc` file), and then opting specific targets out temporarily by disabling the feature in the BUILD file by putting `features = ["-layering_check"]` attribute into the C++ rule. In order to do that we need to generate a module map for the toolchain. This is done by taking any file present transitively in one of the default include directories reported by the compiler. For system installed compilers this map can be quite big as they often look into directories such as `/usr/include` etc. As anecdata, the header map on my home machine is ~ 27K lines long. Computing this map takes too much time when implemented in idiomatic Starlark (~16s on my machine), therefore I've created a shell script that does the work faster (~2s on my machine). The map is only created when the compiler used is `clang`. If the compiler is `clang` is detected by running `$CC -v` and searching for string "clang" :) Closes #11440. PiperOrigin-RevId: 313173938
- Loading branch information
1 parent
f096532
commit 8b9f746
Showing
6 changed files
with
289 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
#!/bin/bash | ||
# | ||
# Copyright 2020 The Bazel Authors. All rights reserved. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
set -euo pipefail | ||
|
||
# Load the test setup defined in the parent directory | ||
CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
source "${CURRENT_DIR}/../integration_test_setup.sh" \ | ||
|| { echo "integration_test_setup.sh not found!" >&2; exit 1; } | ||
|
||
function write_files { | ||
mkdir -p hello || fail "mkdir hello failed" | ||
cat >hello/BUILD <<EOF | ||
cc_binary( | ||
name = 'hello', | ||
srcs = ['hello.cc'], | ||
deps = [":hello_lib"], | ||
) | ||
cc_library( | ||
name = "hello_lib", | ||
srcs = ["hello_private.h", "hellolib.cc"], | ||
hdrs = ["hello.h"], | ||
deps = [":base"], | ||
) | ||
cc_library( | ||
name = "base", | ||
srcs = ["base.cc"], | ||
hdrs = ["base.h"], | ||
) | ||
EOF | ||
cat >hello/hello.h <<EOF | ||
#include "hello_private.h" | ||
int hello(); | ||
EOF | ||
|
||
cat >hello/hello_private.h <<EOF | ||
int helloPrivate(); | ||
EOF | ||
|
||
cat >hello/base.h <<EOF | ||
int base(); | ||
EOF | ||
|
||
cat >hello/base.cc <<EOF | ||
#include "base.h" | ||
int base() { | ||
return 42; | ||
} | ||
EOF | ||
|
||
cat >hello/hellolib.cc <<EOF | ||
#include "hello.h" | ||
#include "base.h" | ||
int helloPrivate() { | ||
return base(); | ||
} | ||
int hello() { | ||
return helloPrivate(); | ||
} | ||
EOF | ||
|
||
cat >hello/hello.cc <<EOF | ||
#ifdef private_header | ||
#include "hello_private.h" | ||
int main() { | ||
return helloPrivate() - 42; | ||
} | ||
#elif defined layering_violation | ||
#include "base.h" | ||
int main() { | ||
return base() - 42; | ||
} | ||
#else | ||
#include "hello.h" | ||
int main() { | ||
return hello() - 42; | ||
} | ||
#endif | ||
EOF | ||
} | ||
|
||
# TODO(hlopko): Add a test for a "toplevel" header-only library | ||
# once we have parse_headers support in cc_configure. | ||
function test_bazel_layering_check() { | ||
if is_darwin; then | ||
echo "This test doesn't run on Darwin. Skipping." | ||
return | ||
fi | ||
|
||
local -r clang_tool=$(which clang) | ||
if [[ ! -x ${clang_tool:-/usr/bin/clang_tool} ]]; then | ||
echo "clang not installed. Skipping test." | ||
return | ||
fi | ||
|
||
write_files | ||
|
||
CC="${clang_tool}" bazel build \ | ||
//hello:hello --linkopt=-fuse-ld=gold --features=layering_check \ | ||
&> "${TEST_log}" || fail "Build with layering_check failed" | ||
|
||
bazel-bin/hello/hello || fail "the built binary failed to run" | ||
|
||
if [[ ! -e bazel-bin/hello/hello.cppmap ]]; then | ||
fail "module map files were not generated" | ||
fi | ||
|
||
if [[ ! -e bazel-bin/hello/hello_lib.cppmap ]]; then | ||
fail "module map files were not generated" | ||
fi | ||
|
||
# Specifying -fuse-ld=gold explicitly to override -fuse-ld=/usr/bin/ld.gold | ||
# passed in by cc_configure because Ubuntu-16.04 ships with an old | ||
# clang version that doesn't accept that. | ||
CC="${clang_tool}" bazel build \ | ||
--copt=-D=private_header \ | ||
//hello:hello --linkopt=-fuse-ld=gold --features=layering_check \ | ||
&> "${TEST_log}" && fail "Build of private header violation with "\ | ||
"layering_check should have failed" | ||
expect_log "use of private header from outside its module: 'hello_private.h'" | ||
|
||
CC="${clang_tool}" bazel build \ | ||
--copt=-D=layering_violation \ | ||
//hello:hello --linkopt=-fuse-ld=gold --features=layering_check \ | ||
&> "${TEST_log}" && fail "Build of private header violation with "\ | ||
"layering_check should have failed" | ||
expect_log "module //hello:hello does not depend on a module exporting "\ | ||
"'base.h'" | ||
} | ||
|
||
run_suite "test layering_check" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
#!/bin/sh | ||
# Copyright 2020 The Bazel Authors. All rights reserved. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
set -eu | ||
|
||
echo 'module "crosstool" [system] {' | ||
|
||
for dir in $@; do | ||
find -L "${dir}" -type f 2>/dev/null | sort | uniq | while read header; do | ||
echo " textual header \"${header}\"" | ||
done | ||
done | ||
|
||
echo "}" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.