Using nix infrastructure to reliably generate `compile_commands.json`

Just wanted to add on in case anyone else is trying to solve this.

I really appreciated the danielbarter’s mini_compile_commands, but I wanted something a bit simplier and here is what I came up with:

{
  pkgs,
  stdenv ? pkgs.stdenv
} :
let
  jq = "${pkgs.jq}/bin/jq";
  cc-wrapper-hook = pkgs.writeShellScriptBin "cc-wrapper-hook"  ''
    OUTDB="$(pwd)_$(date +"%s%N").ccdb"
    OUTFILE=""
    INFILE=""
    for ((i=0; i<''${#params[@]}; i++)); do
      case ''${params[$i]} in
        -o)
          if [ $((i+1)) -lt ''${#params[@]} ]; then
            OUTFILE="''${params[$((i + 1))]}"
            OUTDB="$OUTFILE.ccdb"
            OUTFILE="$(realpath $OUTFILE)"
          fi
          ;;
        -c)
          if [ $((i+1)) -lt ''${#params[@]} ]; then
            INFILE="''${params[$((i + 1))]}"
            INFILE="$(realpath $INFILE)"
          fi
          ;;
        esac
    done

    PARAMS=($compiler ''${extraBefore+"''${extraBefore[@]}"} ''${params+"''${params[@]}"} ''${extraAfter+"''${extraAfter[@]}"})
    PARAMS_JSON="$(printf '%s\n' "''${PARAMS[@]}" | ${jq} -R . | jq -s .)"

    jq -n --argjson args "$PARAMS_JSON" --arg directory "$(pwd)" --arg file "$INFILE" --arg output "$OUTFILE" \
    '{
      arguments: $args,
      directory: $directory,
      file: $file,
      output: $output
    }' > $OUTDB
  '';
  cc-hook = ''
    ln -s ${cc-wrapper-hook}/bin/cc-wrapper-hook $out/nix-support/cc-wrapper-hook
  '';
  collect-compile-commands = pkgs.writeShellScriptBin "collect-compile-commands" ''
    echo "Collecting compilation commands in current directory."
    find . -type f -name '*.ccdb' -print0 | while IFS= read -r -d $'\0' file; do
      jq -c '.' "$file"
    done | jq -s '.' > compile_commands.json
    echo "Compilation database written to $(realpath compile_commands.json)"
  '';
in stdenv.override (old: {
  cc = old.cc.overrideAttrs (final: previous: {
    installPhase = previous.installPhase or "" + cc-hook;
  });
  extraBuildInputs = old.extraBuildInputs or [] ++ [collect-compile-commands];
})

And I’ve been using it my flake something like this:

let
  stdenv = import ./ccdbenv.nix { inherit pkgs stdenv; };
in stdenv.mkDerivation {
  ...
};

From there, running builds will drop .ccdb files next to the build artifacts, and a command collect-compile-commands is used to collect all the contents of the ccdb files and assemble them into a usable compile_commands.json

Anyways, mini_compile_commands was great, I just wanted something a bit simpler and easier to deploy for other people in my build system.

2 Likes