├── LICENSE ├── README.md ├── images ├── include-detective-after.gif ├── include-detective-assert-ndebug.gif ├── include-detective-assert.gif ├── include-detective-before.gif ├── include-detective-helloworldc.gif ├── include-detective-helloworldcxx.gif ├── include-detective-iostream.gif ├── include-detective-stdio.gif ├── include-detective-windowsh-lean-and-mean.gif └── include-detective-windowsh.gif └── include-detective /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Include Detective 2 | 3 | Simple shell script that analyzes the include chain of a C/C++ file or system header. 4 | 5 | ![](/images/include-detective-windowsh-lean-and-mean.gif) 6 | 7 | 8 | ## How does it work? 9 | 10 | Include Detective is a bash script that runs the compiler on a dummy C or C++ file with just an `#include` directive. The `#include` points to the path or system header specified as argument to the script. 11 | 12 | The compiler (GCC and Clang family of compilers) is invoked twice with flags `-E` and `-dM`. 13 | 14 | The `-E` flag stops compilation after the preprocessing stage, resulting in an output where all headers have been inlined and preprocessor directives and macros expanded. 15 | 16 | The `-dM` flag tells the compiler to dump all preprocessor defines (both builtin and the ones defined in the included files). 17 | 18 | These outputs are then parsed and statistics printed as you can see above. 19 | 20 | 21 | ## How to use 22 | 23 | 24 | Usage: include-detective [options] 25 | Print include statistics about a C/C++ file or system header 26 | 27 | Options: 28 | -p print the preprocessed source instead of 29 | computing stats 30 | -d print the preprocessor defines 31 | -i print nested includes 32 | -h show this help 33 | -x is a C++ file 34 | 35 | Environment variables: 36 | CC path to the compiler executable 37 | CFLAGS compilation flags to pass to the compiler. 38 | these flags cannot contain filenames or 39 | output specifiers (e.g. -o) 40 | MODE specify MODE=c++ to process as C++ 41 | 42 | Environment variables can be set on a per-command basis by prefixing the command like so: 43 | `CC=clang CFLAGS="-std=c99 -fno-builtin" stdio.h` 44 | 45 | ## Why should I use it? 46 | 47 | *Include Detective* can be useful when you are looking to eliminate headers in order to **speed up compile times** when using [single translation unit builds](https://en.wikipedia.org/wiki/Single_Compilation_Unit), in favor of [precompiled headers](https://en.wikipedia.org/wiki/Precompiled_header). 48 | 49 | Take a look at the blog post [Include Detective: Keep An Eye on Those Includes](https://metricpanda.com/rival-fortress-update-38-include-detective-keep-an-eye-on-those-includes) for more details. 50 | 51 | 52 | ## Installation 53 | 54 | ### Linux/macOS 55 | 56 | 1. Clone the repository and `chmod +x` the script to make it executable. 57 | 2. Run it passing the path to a source file or a system header file. 58 | 59 | ### Windows 60 | 61 | Haven't tried, but you can probably run it on a Linux-like environment like Cygwin or using the Windows Subsystem for Linux if you are on Windows 10. 62 | 63 | ## Example 64 | 65 | The following is an example of a small console program before and after includes to system headers have been removed. 66 | Only the symbols actually used by the program have been kept (e.g. `externs`, `typedefs` and `defines`). 67 | 68 | Compile time went from 0.8s to 0.5s after the trimming process. 69 | 70 | ![](/images/include-detective-before.gif) 71 | 72 | ![](/images/include-detective-after.gif) 73 | 74 | 75 | ## License 76 | 77 | [Public Domain](/LICENSE) 78 | 79 | 80 | -------------------------------------------------------------------------------- /images/include-detective-after.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetricPanda/include-detective/daaa72419a4ed388248c61d74fe6f0b004dccfba/images/include-detective-after.gif -------------------------------------------------------------------------------- /images/include-detective-assert-ndebug.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetricPanda/include-detective/daaa72419a4ed388248c61d74fe6f0b004dccfba/images/include-detective-assert-ndebug.gif -------------------------------------------------------------------------------- /images/include-detective-assert.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetricPanda/include-detective/daaa72419a4ed388248c61d74fe6f0b004dccfba/images/include-detective-assert.gif -------------------------------------------------------------------------------- /images/include-detective-before.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetricPanda/include-detective/daaa72419a4ed388248c61d74fe6f0b004dccfba/images/include-detective-before.gif -------------------------------------------------------------------------------- /images/include-detective-helloworldc.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetricPanda/include-detective/daaa72419a4ed388248c61d74fe6f0b004dccfba/images/include-detective-helloworldc.gif -------------------------------------------------------------------------------- /images/include-detective-helloworldcxx.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetricPanda/include-detective/daaa72419a4ed388248c61d74fe6f0b004dccfba/images/include-detective-helloworldcxx.gif -------------------------------------------------------------------------------- /images/include-detective-iostream.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetricPanda/include-detective/daaa72419a4ed388248c61d74fe6f0b004dccfba/images/include-detective-iostream.gif -------------------------------------------------------------------------------- /images/include-detective-stdio.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetricPanda/include-detective/daaa72419a4ed388248c61d74fe6f0b004dccfba/images/include-detective-stdio.gif -------------------------------------------------------------------------------- /images/include-detective-windowsh-lean-and-mean.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetricPanda/include-detective/daaa72419a4ed388248c61d74fe6f0b004dccfba/images/include-detective-windowsh-lean-and-mean.gif -------------------------------------------------------------------------------- /images/include-detective-windowsh.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetricPanda/include-detective/daaa72419a4ed388248c61d74fe6f0b004dccfba/images/include-detective-windowsh.gif -------------------------------------------------------------------------------- /include-detective: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Include Detective v0.6 - by Unspongeful (https://metricpanda.com) 4 | # Public Domain 5 | 6 | # Version history 7 | # v0.6 - Support CC enviroment variable that contains spaces in the path 8 | # v0.5 - Add support for multiple input files 9 | # v0.4 - Add option to set C++ mode using MODE env variable 10 | # v0.3 - Add option to print includes 11 | # v0.2 - Use CFLAGS environment variable to pass parameters to compiler 12 | # v0.1 - Initial release 13 | 14 | # Tested with GCC and Clang. 15 | 16 | set -eou pipefail 17 | 18 | printhelp() 19 | { 20 | echo "Usage: $(basename "$0") [options] ..." 21 | echo "Print include statistics about one or more C/C++ file" 22 | echo " or system headers." 23 | echo "" 24 | echo "Options:" 25 | echo " -p print the preprocessed source instead of" 26 | echo " computing stats" 27 | echo " -d print the preprocessor defines" 28 | echo " -i print nested includes" 29 | echo " -h show this help" 30 | echo " -x is a C/C++ file" 31 | echo "" 32 | echo "Environment variables:" 33 | echo " CC path to the compiler executable" 34 | echo " CFLAGS compilation flags to pass to the compiler." 35 | echo " these flags cannot contain filenames or" 36 | echo " output specifiers (e.g. -o)" 37 | echo " MODE specify MODE=c++ to process as C++" 38 | } 39 | 40 | # If CC env variable is not defined default to cc 41 | if [ -z ${CC+x} ]; then 42 | CC=cc 43 | fi 44 | 45 | if [ -z ${CFLAGS+x} ]; then 46 | CFLAGS= 47 | fi 48 | 49 | # Table width 50 | if [ -z ${WIDTH+x} ]; then 51 | WIDTH=70 52 | fi 53 | 54 | joinarray() 55 | { 56 | local IFS="," 57 | echo "$*"; 58 | } 59 | 60 | main() 61 | { 62 | c_string="" 63 | for filename in "$@"; do 64 | # If the source filename points to an existing file 65 | # include relative to working directory, otherwise 66 | # use system include 67 | if [ -f "$filename" ]; then 68 | printf -v c_string "$c_string#include \"$filename\"\n" 69 | else 70 | printf -v c_string "$c_string#include <$filename>\n" 71 | fi 72 | done 73 | preprocessor_output=$(echo "$c_string" | "$CC" $COMPILER_ARGS -E $CFLAGS -) 74 | defines_output=$(echo "$c_string" | "$CC" $COMPILER_ARGS -E $CFLAGS -dM -) 75 | nested_includes=$(echo "$preprocessor_output" | { grep -e '^# [0-9]* "[^<]' || true; }) 76 | 77 | if [ $PRINT_SOURCE -eq 1 ]; then 78 | echo "$preprocessor_output" | sed '/^\s*$/d' 79 | fi 80 | if [ $PRINT_DEFINES -eq 1 ]; then 81 | echo "$defines_output" 82 | fi 83 | if [ $PRINT_INCLUDES -eq 1 ]; then 84 | echo "$nested_includes" | cut -d' ' -f 3 | sort | uniq 85 | fi 86 | exit_early=$(($PRINT_SOURCE+$PRINT_INCLUDES+$PRINT_DEFINES)) 87 | if [ $exit_early -gt 0 ]; then 88 | exit 0 89 | fi 90 | 91 | # Lines of code 92 | total_lines=$(echo "$preprocessor_output" | wc -l) 93 | non_comment_lines=$(echo "$preprocessor_output" | sed '/^# /d' | wc -l) 94 | non_blank_lines=$(echo "$preprocessor_output" | sed '/^\s*$/d' | wc -l) 95 | blank_lines=$(($total_lines-$non_blank_lines)) 96 | comment_lines=$(($total_lines-$non_comment_lines)) 97 | code_lines=$(($non_comment_lines-$blank_lines)) 98 | 99 | # Nested includes 100 | # https://gcc.gnu.org/onlinedocs/cpp/Preprocessor-Output.html 101 | total_includes=$(echo "$nested_includes" | cut -d' ' -f 3 | sort | uniq | wc -l) 102 | non_system_include=$(echo "$nested_includes" | sed '/".*3/d' | cut -d' ' -f 3 | sort | uniq | wc -l) 103 | system_include=$(($total_includes-non_system_include)) 104 | 105 | # Preprocessor defines 106 | total_defines=$(echo "$defines_output" | wc -l) 107 | compiler_defines=$(echo "" | "$CC" $COMPILER_ARGS -E $CFLAGS -dM - | wc -l) 108 | include_defines=$(($total_defines-$compiler_defines)) 109 | 110 | joined_sources=$(joinarray "$@") 111 | printheader "$joined_sources" "Lines of code" "Preprocessor defines" "Nested includes" 112 | 113 | printcell $code_lines "code" 114 | printcell $include_defines "from includes" 115 | printcell $non_system_include "user defined" 116 | endcolumn 117 | 118 | printcell $comment_lines "directives" 119 | printcell $compiler_defines "from compiler" 120 | printcell $system_include "system" 121 | endcolumn 122 | 123 | printfooter $non_blank_lines $total_defines $total_includes 124 | } 125 | 126 | printseparator() 127 | { 128 | printf "%${SEPARATOR_WIDTH}s\n" | tr ' ' - 129 | } 130 | 131 | printheader() 132 | { 133 | printf "%-9s $B${CC}$R ($MODE)\n" "Compiler:" 134 | printf "%-9s $B${CFLAGS}$R\n" "Flags:" 135 | printf "%-9s $B$1$R\n" "File:" 136 | printf "\n$B%-${COLUMN_WIDTH}s%-${COLUMN_WIDTH}s%-${COLUMN_WIDTH}s$R\n" "$2" "$3" "$4" 137 | printseparator 138 | } 139 | 140 | printcell() 141 | { 142 | local color="" 143 | if [ $1 -eq 0 ]; then 144 | color="" 145 | fi 146 | printf "%-${TEXT_WIDTH}s$color%5d$R " "$2" $1 147 | } 148 | 149 | endcolumn() 150 | { 151 | printf "\n" 152 | } 153 | 154 | printfooter() 155 | { 156 | printseparator 157 | printf "%-${TEXT_WIDTH}s$B%${FIRST_COLUMN_WIDTH}d%${COLUMN_WIDTH}d%${COLUMN_WIDTH}d$R\n" "Totals:" "$1" "$2" "$3" 158 | } 159 | 160 | # Pretty colors only in terminal 161 | if [ -t 1 ]; then 162 | R="\033[0m" # Reset 163 | G="\033[37m" # Gray 164 | B="\033[1m" # Bold 165 | else 166 | R="" 167 | G="" 168 | B="" 169 | fi 170 | 171 | COLUMN_WIDTH=$((WIDTH/3)) 172 | SEPARATOR_WIDTH=$((WIDTH-4)) 173 | TEXT_WIDTH=$((COLUMN_WIDTH-8)) 174 | FIRST_COLUMN_WIDTH=$((COLUMN_WIDTH-(TEXT_WIDTH+3))) 175 | 176 | COMPILER_ARGS="" 177 | PRINT_SOURCE=0 178 | PRINT_DEFINES=0 179 | PRINT_INCLUDES=0 180 | while getopts "pdxhi" opt; do 181 | case $opt in 182 | i) 183 | PRINT_INCLUDES=1 184 | ;; 185 | p) 186 | PRINT_SOURCE=1 187 | ;; 188 | x) 189 | MODE="C++" 190 | COMPILER_ARGS="-xc++" 191 | ;; 192 | d) 193 | PRINT_DEFINES=1 194 | ;; 195 | h) 196 | printhelp 197 | exit 1 198 | ;; 199 | \?) 200 | echo "Invalid option: -$opt" >&2 201 | exit 1 202 | ;; 203 | esac 204 | done 205 | 206 | shift $((OPTIND-1)) 207 | 208 | case ${MODE:-} in 209 | c++|C++|CXX|cxx) 210 | COMPILER_ARGS="-xc++" 211 | MODE="C++" 212 | ;; 213 | *) 214 | MODE="C" 215 | ;; 216 | esac 217 | 218 | if [ $# -eq 0 ]; then 219 | printhelp 220 | exit 1 221 | else 222 | main $@ 223 | fi 224 | --------------------------------------------------------------------------------