diff --git a/docs/usage.md b/docs/usage.md index 42731d92e..17ed063f8 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -26,6 +26,22 @@ want to generate and use a `compile_commands.json` to ensure the rewriter preprocesses each source file with the same command-line arguments as when it is compiled. +### Compile Command Paths + +Note that `libclangTooling`, which `ia2-rewriter` uses, is very finicky +about relative paths, so we suggest making all relative paths absolute. +Furthermore, `ia2-rewriter` itself sometimes uses paths as keys, +so we suggest making all paths canonical. We have a script, +[`canonicalize_compile_command_paths.py`](../tools/rewriter/canonicalize_compile_command_paths.py), +that does this automatically (doing its best to detect paths embedded in args). +`libclangTooling` also requires compilation databases to be named exactly +`compile_commands.json`, so this script assumes that as well, +and must be run in the directory of the `compile_commands.json`, like this: + +```sh +(cd $build_directory && $ia2_dir/tools/rewriter/canonicalize_compile_command_paths.py) +``` + ## Manual source changes ### Defining compartments diff --git a/tools/rewriter/canonicalize_compile_command_paths.py b/tools/rewriter/canonicalize_compile_command_paths.py new file mode 100755 index 000000000..bfd376fd0 --- /dev/null +++ b/tools/rewriter/canonicalize_compile_command_paths.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 + +# /// script +# requires-python = ">=3.4" +# dependencies = [ +# "pathlib", +# ] +# /// + +import json +from pathlib import Path + +def main(): + """ + Assuming we're in a build directory containing a `compile_commands.json`, + canonicalize (including making absolute) all paths in every compile command, + including as part of arguments (detect as a best effort). + + libclangTooling is finicky with relative paths, + and it and other tools don't always handle non-canonical paths well, + so making all paths in a `compile_commands.json` canonical is helpful. + """ + + cc_db = Path("compile_commands.json") + cc_text = cc_db.read_text() + cmds = json.loads(cc_text) + for cmd in cmds: + dir = Path(cmd["directory"]).resolve() + cmd["directory"] = str(dir) + + def convert_path(path: str) -> str: + path: Path = Path(path) + if not path.is_absolute(): + path = dir / path + path = path.resolve() + return str(path) + + cmd["file"] = convert_path(cmd["file"]) + if "output" in cmd: + cmd["output"] = convert_path(cmd["output"]) + + def try_convert_arg(arg: str) -> str: + for prefix in ["", "-I"]: + if not arg.startswith(prefix): + continue + path = Path(arg[len(prefix):]) + if not path.is_absolute(): + path = dir / path + if path.exists(): + path = path.resolve() + return prefix + str(path) + return arg + + if "command" in cmd: + cmd["command"] = " ".join(try_convert_arg(arg) for arg in cmd["command"].split()) + if "arguments" in cmd: + cmd["arguments"] = [try_convert_arg(arg) for arg in cmd["arguments"]] + cc_text = json.dumps(cmds, indent=4) + cc_db.write_text(cc_text) + +if __name__ == "__main__": + main()