Skip to content

Search and Fix C/C++ includes, after moving source and header files to new directories.

License

Notifications You must be signed in to change notification settings

mcourteaux/sfincludes

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

20 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

SFincludes

SFincludes (shorthand for Search-and-Fix Includes) is a tool that goes through all of your source and header files in a given directory and tries to fix all #include "" (and optionally #include <>) preprocessor instructions it can find, by looking for files with similar names, in a set of directories you specify.

πŸ‘€ Why SFincludes?

This is the go-to solution to fix your include statements after reorganizing your files into different directories. Instead of going through a loop of [compile, fix include error, repeat]; SFincludes analyzes your project and automatically fixes all your include statements.

⭐ Features

  • Find the best match of the incorrect include path to fix it.
  • Handles both project-local #include "" and project-external #include <> statements.
  • Preserves comments after the #include statement.
  • Detailed dry-run report before actually rewriting your includes.
  • Optional fuzzy name-matching in case you also rename header files.
  • Fast and small: binary is less than 200kB.

βš™οΈ How?

How does it fix the includes? It first gathers all header files (.h or .hpp) found in the specified include directories. Next it processes all files one-by-one found in the specified source directories, and then fixes every include with one of the found headers. Which header is chosen depends on the configuration you pass such as fuzzy filename matching, allowing changing system-includes to user-includes and visa-versa.

The selected header is chosen by file name. By default the filename should match exactly. When there are multiple files with the exact same name found during the header-searching process, the one with the minimal changes in path will be preferred (the other options will be reported as alternatives that were considered but not chosen). Optionally, fuzzy matching can be done, using a customized Levenshtein distance (also known as the "edit distance").

Optionally the .h extension can be replaced by a .hpp extension for all your header files. Note that this tool will not try to figure out whether or not a particular file is a C or a C++ header. Note that this feature is unsuitable for projects that use a mix of C and C++.

πŸ“₯ Installation

git clone https://github.com/mcourteaux/sfincludes
cd sfincludes
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
make
sudo make install

πŸ–₯️ Command line arguments

  --help                     Produce help message.
  --src arg                  Add a source directory to process. [repeat --src
                             to specify more]
  --user-include-path arg    Add user include search path directory (cfr. gcc
                             -Ipath) [repeat --user-include-path to specify
                             more]
  --sys-include-path arg     Add system include search path directory (cfr. gcc
                             -isystem). [repeat --sys-include-path to specify
                             more]
  --fuzzy arg (=0)           Maximal filename edit distance (costs: insert=4,
                             change=2, capitalize=1).
  --process-system-includes  Also process #include <> statements.
  --system-to-user           Replace #include <> with #include "" when the file
                             is found user include search path. Only when
                             --process-system-includes.
  --user-to-system           Replace #include "" with #include <> when the file
                             is found in the system include search path.
  --prefer-relative-to-root  Also rewrite correct includes to be relative to
                             their corresponding search path root.
  --rename-hpp               Rename .h headers files to .hpp.
  --no-dry-run               Actually perform the changes.
  --verbose                  Be verbose.
  --omit-untouched           Do not list the untouched files. Useful for
                             reviewing.
  --omit-system-failed       Do not list the system includes which were not
                             resolved. Useful for reviewing.

Meaning of the paths:

  • an include-path is a path which will be used to find header files which can be included. If an include can be mapped onto a header in this folder, the include will be rewritten, relative to this path. You can add as many include paths as you like (by repeating the flag).
  • a src path is a directory in which it will search for C/C++ files (headers and source files) to fix: it will rewrite the #include statements in these files. You can add as many source folders as you like, which is typical if you have a src/ and an include/ directory in your project (note that you likely also want to fix the include statements within the header files itself).

Often, these paths will be the same. Note that you always need to provide both.

By default, sfincludes will output what it would do. Note that there is a summary of the includes it fixed or left untouched. It is advised to first perform a dry run and inspect the summary to see if there are problems (i.e., the total number of successfully processed includes does not match the total number of includes found). Once confident, changes can actually be executed by running sfincludes with the flag --no-dry-run.

If you use the --rename-hpp flag, you will most likely want to set the --fuzzy argument to 8, as the filenames will have changed with a distance of 8, i.e., "add p (cost 4), and add p (cost 4)". Leaving the --fuzzy argument to zero, will rename the files, but will fail finding the includes, and leave the those includes untouched.

πŸ”₯ Example

In this example, I moved a couple of related header files to their own separate subdirectory within the project file structure. For example neonraw/JRS.hpp moved to neonraw/jrs/JRS.hpp along with some related files. For this project, I have a script set up to invoke SFincludes:

#!/bin/bash

SFI=sfincludes
ZDIR=$(dirname "$0")

THIRD_PARTY_SYS_INCLUDES="\
  --sys-include-path ./NeonRAW/ext/tracy/public/ \
  --sys-include-path ./3rd/halide_repo/distrib/include/"

# NeonRAW main
$SFI --process-system-includes --prefer-relative-to-root --system-to-user --user-to-system \
  --src $ZDIR/NeonRAW/src \
  --src $ZDIR/NeonRAW/include \
  --src $ZDIR/NeonRAW/generators \
  --user-include-path $ZDIR/NeonRAW/include \
  --sys-include-path $ZDIR/color/include \
  $THIRD_PARTY_SYS_INCLUDES $@

which outputs something like this (only an excerpt, stripped away most output for demonstration puproses):

User inlucde path : ./NeonRAW/include
System inlucde path : ./color/include
System inlucde path : ./NeonRAW/ext/tracy/public/
System inlucde path : ./3rd/halide_repo/distrib/include/
Source : ./NeonRAW/src
Source : ./NeonRAW/include
Source : ./NeonRAW/generators
Process system includes.
Convert system includes to user includes when a corresponding file is found in the user include search path.
Convert user includes to system includes when a corresponding file is found in the system include search path.
Prefer include paths to be always written relative to the root.
Fuzzy search : 0
Dry run. (Use --no-dry-run to effectively write changes back to filesystem.)

Index headers in: "./NeonRAW/include"
Index headers in: "./color/include"
Index headers in: "./NeonRAW/ext/tracy/public/"
Index headers in: "./3rd/halide_repo/distrib/include/"


Processing source directory: ./NeonRAW/src...
    Process ./NeonRAW/src/neonraw/image_processing/BilateralGrid.cpp ...
        βœ… Untouched include: "neonraw/image_processing/BilateralGrid.hpp"
        βœ… Untouched include: "neonraw/Engine.hpp"
        βœ… Untouched include: "neonraw/performance/perfrep.hpp"
        πŸ’„ Change include type: <neonraw/datasource/DataSource.hpp>  ->  "neonraw/datasource/DataSource.hpp"
        ❓ Failed to fix include: <Eigen/LU>
        ❓ Failed to fix include: <cmath>
        πŸ’„ Change include type: "tracy/Tracy.hpp"  ->  <tracy/Tracy.hpp>
        ❓ Failed to fix include: <spdlog/spdlog.h>
        ❓ Failed to fix include: <halide_image_io.h>
    Process ./NeonRAW/src/neonraw/ioutils.cpp ...
        βœ… Untouched include: "neonraw/ioutils.hpp"
    Process ./NeonRAW/src/neonraw/datasource/SentinelDataSource.cpp ...
        βœ… Untouched include: "neonraw/datasource/SentinelDataSource.hpp"
        βœ… Untouched include: <HalideRuntime.h>
    Process ./NeonRAW/src/neonraw/datasource/RawSpeedDataSource.cpp ...
        βœ… Untouched include: "neonraw/datasource/RawSpeedDataSource.hpp"
        βœ… Untouched include: "neonraw/datasource/RawSpeedByproducts.hpp"
        βœ… Untouched include: "neonraw/utils.hpp"
        βœ… Untouched include: "neonraw/adobe_coeff.h"
        βœ… Untouched include: "neonraw/image_processing/BilinearDemosaicer.hpp"
        βœ… Untouched include: "neonraw/math/matrix_pseudo_inverse.hpp"
        ❓ Failed to fix include: <neonraw_RAW_to_xyY.h>
        ❓ Failed to fix include: <decoders/RawDecoderException.h>
        βœ… Untouched include: <color.hpp>
        ❓ Failed to fix include: <Eigen/LU>
        ❓ Failed to fix include: <spdlog/spdlog.h>
        πŸ’„ Change include type: "tracy/Tracy.hpp"  ->  <tracy/Tracy.hpp>
        ❓ Failed to fix include: <openssl/sha.h>
    Process ./NeonRAW/src/neonraw/datasource/MemoryBufferDataSource.cpp ...
        βœ… Untouched include: "neonraw/datasource/MemoryBufferDataSource.hpp"
        βœ… Untouched include: <HalideRuntime.h>
    Process ./NeonRAW/src/neonraw/datasource/DataSource.cpp ...
        βœ… Untouched include: "neonraw/datasource/DataSource.hpp"
        βœ… Untouched include: "neonraw/mat.hpp"
        βœ… Untouched include: "neonraw/math/matrix_pseudo_inverse.hpp"
        πŸ‘• Replace include path: "neonraw/JRS.hpp"  ->  "neonraw/jrs/JRS.hpp"  (distance: fn=0; dir=16) from "./NeonRAW/include"
        βœ… Untouched include: <color.hpp>

<stripped away a lot...>

    Process ./NeonRAW/include/neonraw/datasource/SentinelDataSource.hpp ...
        πŸ‘• Replace include path: "DataSource.hpp"  ->  "neonraw/datasource/DataSource.hpp"  (distance: fn=0; dir=0) from "./NeonRAW/include"
           - Alternative: neonraw/datasource/DataSource.hpp  (distance: fn=0; dir=3) from "./NeonRAW/include"

Replaced path: 15 / 348
Sys-to-user  : 2 / 348
User-to-sys  : 11 / 348
Untouched    : 131 / 348
Failed       : 189 / 348

Notice how I had Tracy (a third-party library) included with #include "" syntax, which I prefer to change to #include <>, as it is external to the current project. I indicated this preference by passing --user-to-system. The Tracy include path itself was passed as a system search path via --sys-include-path. SFincludes uses this information and rewrites the include to use <...> instead of "..." (indicated with the πŸ’„ emoji).

In the report for last file in this example, notice how it rewrites a file-relative path to search-path relative path. In include/neonraw/datasource/ there is both DataSource.hpp and SentinelDataSource.hpp. When processing SentinelDataSource.hpp, SFincludes detects the #include "DataSource.hpp" refers to the file in the same directory. Because I pass --prefer-relative-to-root, it rewrites this to the full path: neonraw/datasource/DataSource.hpp. Path rewrites are indicated with the πŸ‘• emoji.

Note that it is not recommended to add actual system includes to the command line arguments, as that just slows down the entire process, without any actual benefit. Additionally, if you miss any of the real system search paths, you risk incorrectly rewriting includes with the wrong target.

When reviewing during the dry-runs and interested in only seeing the changes, you can additionally pass --omit-untouched and --omit-system-failed to get a simplified report. For example:

Processing source directory: /home/martijn/zec/NeonRAW/include...
    Process /home/martijn/zec/NeonRAW/include/neonraw/jrs/Unique.hpp ...
    Process /home/martijn/zec/NeonRAW/include/neonraw/jrs/JRS.hpp ...
        πŸ‘• Replace include path: "JRS_fwd.hpp"  ->  "neonraw/jrs/JRS_fwd.hpp"  (distance: fn=0; dir=0) from "/home/martijn/zec/NeonRAW/include"
           - Alternative: neonraw/jrs/JRS_fwd.hpp  (distance: fn=0; dir=3) from "/home/martijn/zec/NeonRAW/include"
        πŸ‘• Replace include path: "ResourceMemory.hpp"  ->  "neonraw/jrs/ResourceMemory.hpp"  (distance: fn=0; dir=0) from "/home/martijn/zec/NeonRAW/include"
           - Alternative: neonraw/jrs/ResourceMemory.hpp  (distance: fn=0; dir=3) from "/home/martijn/zec/NeonRAW/include"
        πŸ‘• Replace include path: "assert.hpp"  ->  "neonraw/debug/assert.hpp"  (distance: fn=0; dir=12) from "/home/martijn/zec/NeonRAW/include"
        πŸ‘• Replace include path: "build_type.hpp"  ->  "neonraw/debug/build_type.hpp"  (distance: fn=0; dir=12) from "/home/martijn/zec/NeonRAW/include"
        πŸ‘• Replace include path: "DotVisualizer.hpp"  ->  "neonraw/jrs/DotVisualizer.hpp"  (distance: fn=0; dir=0) from "/home/martijn/zec/NeonRAW/include"
           - Alternative: neonraw/jrs/DotVisualizer.hpp  (distance: fn=0; dir=3) from "/home/martijn/zec/NeonRAW/include"
        πŸ‘• Replace include path: "JRS_impl.hpp"  ->  "neonraw/jrs/JRS_impl.hpp"  (distance: fn=0; dir=0) from "/home/martijn/zec/NeonRAW/include"
           - Alternative: neonraw/jrs/JRS_impl.hpp  (distance: fn=0; dir=3) from "/home/martijn/zec/NeonRAW/include"
    Process /home/martijn/zec/NeonRAW/include/neonraw/jrs/ResourceMemory.hpp ...
    Process /home/martijn/zec/NeonRAW/include/neonraw/jrs/DotVisualizer.hpp ...
        πŸ‘• Replace include path: "Unique.hpp"  ->  "neonraw/jrs/Unique.hpp"  (distance: fn=0; dir=0) from "/home/martijn/zec/NeonRAW/include"
           - Alternative: neonraw/jrs/Unique.hpp  (distance: fn=0; dir=3) from "/home/martijn/zec/NeonRAW/include"
        πŸ‘• Replace include path: "demangle.hpp"  ->  "neonraw/debug/demangle.hpp"  (distance: fn=0; dir=12) from "/home/martijn/zec/NeonRAW/include"
    Process /home/martijn/zec/NeonRAW/include/neonraw/jrs/JRS_impl.hpp ...
        πŸ‘• Replace include path: "JRS.hpp"  ->  "neonraw/jrs/JRS.hpp"  (distance: fn=0; dir=0) from "/home/martijn/zec/NeonRAW/include"
           - Alternative: neonraw/jrs/JRS.hpp  (distance: fn=0; dir=3) from "/home/martijn/zec/NeonRAW/include"
        πŸ‘• Replace include path: "demangle.hpp"  ->  "neonraw/debug/demangle.hpp"  (distance: fn=0; dir=12) from "/home/martijn/zec/NeonRAW/include"
    Process /home/martijn/zec/NeonRAW/include/neonraw/jrs/ResourceTracker.hpp ...
        πŸ‘• Replace include path: "JRS.hpp"  ->  "neonraw/jrs/JRS.hpp"  (distance: fn=0; dir=0) from "/home/martijn/zec/NeonRAW/include"
           - Alternative: neonraw/jrs/JRS.hpp  (distance: fn=0; dir=3) from "/home/martijn/zec/NeonRAW/include"

Notice that because of special handling of relative-to-current-file include statements, some headers are reported again as an alternative, but with a higher directory distance. You can ignore these.

βš–οΈ License

This work is distributed under the MIT license: see the LICENSE file.

About

Search and Fix C/C++ includes, after moving source and header files to new directories.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published