mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
287 lines
9.4 KiB
Markdown
287 lines
9.4 KiB
Markdown
# How to run include-what-you-use (IWYU) on the Swift project
|
|
|
|
[include-what-you-use (IWYU)](https://include-what-you-use.org) is a
|
|
Clang-based tool that analyzes `#include`s in a file and makes suggestions to
|
|
add or remove `#include`s based on usage in the code. This has two key benefits:
|
|
|
|
- Removing unused `#include` statements reduces work for the compiler.
|
|
- Adding `#include` statements for usage avoids a refactoring in a header
|
|
file from breaking downstream implementation files due to accidental
|
|
transitive usage.
|
|
|
|
Running IWYU is a bit tricky, so this how-to guide provides the steps for how
|
|
to get it up and running on the Swift project for macOS.
|
|
If you get IWYU working on a different platform and some steps need to be
|
|
changed, please update this document with platform-specific steps.
|
|
|
|
- [Pre-requisites](#pre-requisites)
|
|
- [Cloning and branch checkout](#cloning-and-branch-checkout)
|
|
- [Building IWYU](#building-iwyu)
|
|
- [Running IWYU](#running-iwyu)
|
|
- [Debugging](#debugging)
|
|
|
|
## Pre-requisites
|
|
|
|
- A built Swift project with exported compilation commands.
|
|
By default, compilation commands are generated in the file
|
|
`build/[BuildSystem]-[BuildVariant]/swift-[target]/compile_commands.json`.
|
|
Check that this file is present before proceeding.
|
|
- If this file is missing, try building with
|
|
`CMAKE_EXPORT_COMPILATION_COMMANDS=ON`. If you use `build-script` to
|
|
manage your builds, you can do this with
|
|
```
|
|
swift/utils/build-script <other options> \
|
|
--extra-cmake-options='-DCMAKE_EXPORT_COMPILATION_COMMANDS=ON'
|
|
```
|
|
- Install [`jq`](https://stedolan.github.io/jq/). It's not strictly necessary,
|
|
but we will use it for some JSON munging.
|
|
|
|
## Cloning and branch checkout
|
|
|
|
The directory structure we will be using is
|
|
|
|
```
|
|
swift-project/
|
|
|--- build/
|
|
| |--- [BuildSystem]-[BuildVariant]/
|
|
| | |--- swift-[target]/
|
|
| | | |--- compile_commands.json
|
|
| | | `--- ...
|
|
| | |--- iwyu-[target]/
|
|
| | `--- ...
|
|
| `--- ...
|
|
|--- swift/
|
|
|--- iwyu/
|
|
| |--- src/
|
|
| |--- logs/
|
|
| `--- scripts/
|
|
`--- ...
|
|
```
|
|
|
|
As a running example, the description below uses `[BuildSystem] = Ninja`,
|
|
`[BuildVariant] = ReleaseAssert` and `[target] = macosx-x86_64`.
|
|
|
|
Start with `swift-project` as the working directory.
|
|
|
|
1. Check out IWYU.
|
|
```
|
|
mkdir -p iwyu/src
|
|
git clone https://github.com/include-what-you-use/include-what-you-use.git iwyu/src
|
|
```
|
|
2. Find out the version of the `clang` built recently.
|
|
```
|
|
build/Ninja-ReleaseAssert/llvm-macosx-x86_64/bin/clang --version
|
|
```
|
|
This should say something like `clang version 10.0.0` or similar.
|
|
3. Based on the `clang` version, make sure you check out the correct branch.
|
|
```
|
|
git -C iwyu/src checkout clang_10
|
|
```
|
|
|
|
## Building IWYU
|
|
|
|
1. Configure IWYU with CMake.
|
|
```
|
|
cmake -G Ninja \
|
|
-DCMAKE_PREFIX_PATH=build/Ninja-ReleaseAssert/llvm-macosx-x86_64 \
|
|
-DCMAKE_CXX_STANDARD=14 \
|
|
-B build/Ninja-ReleaseAssert/iwyu-macosx-x86_64 \
|
|
iwyu/src
|
|
```
|
|
2. Build IWYU
|
|
```
|
|
cmake --build build/Ninja-ReleaseAssert/iwyu-macosx-x86_64
|
|
```
|
|
3. Create an extra symlink so IWYU can find necessary Clang headers:
|
|
```
|
|
ln -sF build/Ninja-ReleaseAssert/llvm-macosx-x86_64/lib build/Ninja-ReleaseAssert/iwyu-macosx-x86_64/lib
|
|
```
|
|
4. Spot check IWYU for a basic C example.
|
|
```
|
|
echo '#include <stdint.h>' > tmp.c
|
|
./bin/include-what-you-use tmp.c -E -o /dev/null \
|
|
-I "$(xcrun --show-sdk-path)/usr/include"
|
|
rm tmp.c
|
|
```
|
|
You should see output like:
|
|
```
|
|
tmp.c should add these lines:
|
|
|
|
tmp.c should remove these lines:
|
|
- #include <stdint.h> // lines 1-1
|
|
|
|
The full include-list for tmp.c:
|
|
---
|
|
```
|
|
5. Spot check IWYU for a basic C++ example. Notice the extra C++-specific
|
|
include path.
|
|
```
|
|
echo '#include <string>\n#include <cmath>' > tmp.cpp
|
|
./bin/include-what-you-use tmp.cpp -E -o /dev/null \
|
|
-I "$(clang++ -print-resource-dir)/../../../include/c++/v1" \
|
|
-I "$(xcrun --show-sdk-path)/usr/include"
|
|
rm tmp.cpp
|
|
```
|
|
You should see output like:
|
|
```
|
|
tmp.cpp should add these lines:
|
|
|
|
tmp.cpp should remove these lines:
|
|
- #include <cmath> // lines 2-2
|
|
- #include <string> // lines 1-1
|
|
|
|
The full include-list for tmp.cpp:
|
|
---
|
|
```
|
|
|
|
## Running IWYU
|
|
|
|
1. Create a directory, say `iwyu/scripts`, and copy the following script there.
|
|
|
|
```
|
|
#!/usr/bin/env bash
|
|
|
|
# iwyu_run.sh
|
|
set -eu
|
|
|
|
SWIFT_PROJECT_DIR="$HOME/swift-project"
|
|
SWIFT_BUILD_DIR="$SWIFT_PROJECT_DIR/build/Ninja-ReleaseAssert/swift-macosx-x86_64"
|
|
|
|
pushd "$SWIFT_BUILD_DIR"
|
|
|
|
if [ -f original_compile_commands.json ]; then
|
|
mv original_compile_commands.json compile_commands.json
|
|
fi
|
|
|
|
# HACK: The additional include path needs to be added before other include
|
|
# paths, it doesn't seem to work if we add it at the end.
|
|
# It is ok to rely on the presence of `-D__STDC_LIMIT_MACROS` flag, since
|
|
# it is added by the LLVM CMake configuration for all compilation commands.
|
|
( EXTRA_CXX_INCLUDE_DIR="$(clang++ -print-resource-dir)/../../../include/c++/v1";
|
|
cat compile_commands.json \
|
|
| jq '[.[] | select(.file | test("\\.mm" | "\\.m") | not) | {directory: .directory, command: (.command + " -Wno-everything -ferror-limit=1"), file: .file}]' \
|
|
| sed -e "s|-D__STDC_LIMIT_MACROS |-D__STDC_LIMIT_MACROS -I $EXTRA_CXX_INCLUDE_DIR |" \
|
|
) > filtered_compile_commands.json
|
|
|
|
mv compile_commands.json original_compile_commands.json
|
|
mv filtered_compile_commands.json compile_commands.json
|
|
|
|
mkdir -p "$SWIFT_PROJECT_DIR/iwyu/logs"
|
|
|
|
( PATH="$SWIFT_PROJECT_DIR/iwyu/build/bin:$PATH"; \
|
|
"$SWIFT_PROJECT_DIR/iwyu/include-what-you-use/iwyu_tool.py" -p "$SWIFT_BUILD_DIR"
|
|
) | tee "$SWIFT_PROJECT_DIR/iwyu/logs/suggestions.log"
|
|
|
|
popd
|
|
|
|
```
|
|
|
|
We filter out Objective-C files because IWYU does not support Objective-C.
|
|
If that step is missed, you might hit errors like:
|
|
```
|
|
iwyu.cc:2097: Assertion failed: TODO(csilvers): for objc and clang lang extensions
|
|
```
|
|
|
|
2. Update the `SWIFT_PROJECT_DIR` and `SWIFT_BUILD_DIR` variables based on
|
|
your project and build directories.
|
|
|
|
3. Run the script.
|
|
```
|
|
chmod +x iwyu/scripts/iwyu_run.sh
|
|
iwyu/scripts/iwyu_run.sh
|
|
```
|
|
This will generate a log file under `iwyu/logs/suggestions.log`.
|
|
Note that IWYU might take several hours to run, depending on your system.
|
|
|
|
NOTE: The IWYU README suggests several different ways of running IWYU on a
|
|
CMake project, including using the `CMAKE_CXX_INCLUDE_WHAT_YOU_USE` and
|
|
`CMAKE_C_INCLUDE_WHAT_YOU_USE` variables. At the time of writing, those did
|
|
not reliably work on macOS; suggestions were generated only for specific
|
|
subprojects (e.g. the stdlib) and not others (e.g. the compiler).
|
|
Using CMake variables also requires reconfiguring and rebuilding, which makes
|
|
debugging much more time-consuming.
|
|
|
|
## Debugging
|
|
|
|
While the above steps should work, in case you run into issues, you might find
|
|
the following steps for debugging helpful.
|
|
|
|
### Try different include path ordering
|
|
|
|
If you see errors with `<cmath>`, or similar system headers, one thing that might
|
|
be happening is that the include paths are in the wrong order. Try moving the
|
|
include paths for the corresponding header before/after all other include paths.
|
|
|
|
### Iterate on files one at a time
|
|
|
|
Instead of trying to make changes to the CMake configuration and recompiling
|
|
the whole project, first try working on individual compilation commands as
|
|
emitted in `compile_commands.json` and see if IWYU works as expected.
|
|
|
|
For each command, try replacing the compiler with the `include-what-you-use`
|
|
binary or `iwyu_stub.py` (below) to see if the behavior is as expected.
|
|
You may need to manually add some include paths as in `iwyu_run.sh` above.
|
|
Make sure you update paths in the script before it works.
|
|
|
|
```
|
|
#!/usr/bin/env python3
|
|
|
|
# iwyu_stub.py
|
|
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
|
|
clang_path = "/usr/bin/clang"
|
|
clangxx_path = "/usr/bin/clang++"
|
|
project_dir = "/Users/username/swift-project/"
|
|
iwyu_bin_path = project_dir + "iwyu/build/bin/include-what-you-use"
|
|
log_dir = project_dir + "iwyu/logs/"
|
|
|
|
log_file = open(log_dir + "passthrough.log", "a+")
|
|
|
|
argv = sys.argv
|
|
|
|
def call_with_args(executable_path, args=argv):
|
|
new_argv = args[:]
|
|
new_argv[0] = executable_path
|
|
log_file.write("# about to run:\n{}\n#---\n".format(' '.join(new_argv)))
|
|
sys.exit(subprocess.call(new_argv))
|
|
|
|
# HACK: Relies on the compilation commands generated by CMake being
|
|
# of the form:
|
|
#
|
|
# /path/to/compiler <other options> -c MyFile.ext
|
|
#
|
|
def try_using_iwyu(argv):
|
|
return (argv[-2] == "-c") and ("/swift/" in argv[-1])
|
|
|
|
# Flag for quickly switching between IWYU and Clang for iteration.
|
|
# Useful for checking behavior for different include path combinations.
|
|
if argv[1] == "--forward-to-clangxx":
|
|
call_with_args(clangxx_path, args=([argv[0]] + argv[2:]))
|
|
|
|
# Check that we are getting a compilation command.
|
|
if try_using_iwyu(argv):
|
|
_, ext = os.path.splitext(argv[-1])
|
|
if ext == ".m":
|
|
call_with_args(clang_path)
|
|
elif ext == ".mm":
|
|
call_with_args(clangxx_path)
|
|
elif ext in [".cxx", ".cc", ".cpp", ".c"]:
|
|
call_with_args(iwyu_bin_path)
|
|
log_file.write(
|
|
"# Got a strange file extension.\n{}\n#---\n".format(' '.join(argv)))
|
|
call_with_args(iwyu_bin_path)
|
|
else:
|
|
# got something else, just forward to clang/clang++
|
|
log_file.write(
|
|
"# Not going to try using iwyu.\n{}\n#---\n".format(' '.join(argv)))
|
|
_, ext = os.path.splitext(argv[-1])
|
|
if ext == ".m" or ext == ".c":
|
|
call_with_args(clang_path)
|
|
else:
|
|
call_with_args(clangxx_path)
|
|
```
|