How to correctly generate compile_commands.json for library dependency using PkgConfig?

Hi,

I am on MacOS, and I use nix as my package manager. I am building a C project, and I’d like to use Criterion for my tests. I am able to build my project and run the tests, but my compile_commands.json file is not generated as I expect it to be, and this is bugging me because my editor keeps complaining that my it’s unable to find criterion’s header files. Any pointers here on what I’m doing wrong will be greatly appreciated.

./build/compile_commands.json

Actual

[
{
  "directory": "<path>/linked_list/build/lib",
  "command": "/nix/store/x5vj7kgmimckq45xjjfdga2ib3v6wfg9-clang-wrapper-20.1.6/bin/clang  -I<path>/linked_list/include  -arch arm64 -isysroot /nix/store/5gfsv5n8zhpnl9yhggjpxrxg0jyflwja-apple-sdk-11.3/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk -mmacosx-version-min=11.3 -o CMakeFiles/c_linked_list.dir/linked_list.c.o -c linked_list/lib/linked_list.c",
  "file": "<path>/linked_list/lib/linked_list.c",
  "output": "lib/CMakeFiles/c_linked_list.dir/linked_list.c.o"
},
{
  "directory": "<path>/linked_list/build/tests",
  "command": "/nix/store/x5vj7kgmimckq45xjjfdga2ib3v6wfg9-clang-wrapper-20.1.6/bin/clang  -I<path>/linked_list/include  -arch arm64 -isysroot /nix/store/5gfsv5n8zhpnl9yhggjpxrxg0jyflwja-apple-sdk-11.3/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk -mmacosx-version-min=11.3 -o CMakeFiles/test_alloc_and_free.dir/test_alloc_and_free.c.o -c <path>/linked_list/tests/test_alloc_and_free.c",
  "file": "<path>/linked_list/tests/test_alloc_and_free.c",
  "output": "tests/CMakeFiles/test_alloc_and_free.dir/test_alloc_and_free.c.o"
}
]

Expected

Notice how there is only one -I flag for the tests directory command. I was expecting to see two -I - one for my own include dir, and one for criterion.

Commands I’m using

$ cmake --version
cmake version 3.31.6

CMake suite maintained and supported by Kitware (kitware.com/cmake).

$ cmake -S . -B build
-- The C compiler identification is Clang 20.1.6
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /nix/store/x5vj7kgmimckq45xjjfdga2ib3v6wfg9-clang-wrapper-20.1.6/bin/clang - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Found PkgConfig: /nix/store/irxrmkcns1sjckx7wii2sai156827if6-pkg-config-wrapper-0.29.2/bin/pkg-config (found version "0.29.2")
-- Checking for module 'criterion'
--   Found criterion, version 2.4.2
-- Configuring done (0.4s)
-- Generating done (0.0s)
-- Build files have been written to: <path>/linked_list/build

$ cmake --build build
[ 25%] Building C object lib/CMakeFiles/c_linked_list.dir/linked_list.c.o
[ 50%] Linking C static library libc_linked_list.a
[ 50%] Built target c_linked_list
[ 75%] Building C object tests/CMakeFiles/test_alloc_and_free.dir/test_alloc_and_free.c.o
[100%] Linking C executable test_alloc_and_free
[100%] Built target test_alloc_and_free

$ cmake --build build --target test
Running tests...
Test project <path>/linked_list/build
    Start 1: alloc_and_free
1/1 Test #1: alloc_and_free ...................   Passed    0.17 sec

100% tests passed, 0 tests failed out of 1

Total Test time (real) =   0.17 sec

$ pkg-config --libs criterion
-L/nix/store/p0nnffxh9jrldxz5day2yvpvsa1pq6i3-criterion-2.4.2/lib -Wl,-rpath,/nix/store/p0nnffxh9jrldxz5day2yvpvsa1pq6i3-criterion-2.4.2/lib -lcriterion

$ pkg-config --cflags --libs criterion
-I/nix/store/i3qwlyca06xx1fgdj1pcr4bzsyvfq4by-criterion-2.4.2-dev/include -L/nix/store/p0nnffxh9jrldxz5day2yvpvsa1pq6i3-criterion-2.4.2/lib -Wl,-rpath,/nix/store/p0nnffxh9jrldxz5day2yvpvsa1pq6i3-criterion-2.4.2/lib -lcriterion

$ ls /nix/store/p0nnffxh9jrldxz5day2yvpvsa1pq6i3-criterion-2.4.2
lib share

$ ls /nix/store/p0nnffxh9jrldxz5day2yvpvsa1pq6i3-criterion-2.4.2/lib
libcriterion.3.dylib    libcriterion.a          libcriterion.dylib

$ ls /nix/store/i3qwlyca06xx1fgdj1pcr4bzsyvfq4by-criterion-2.4.2-dev
include         lib             nix-support

$ ls /nix/store/i3qwlyca06xx1fgdj1pcr4bzsyvfq4by-criterion-2.4.2-dev/lib/pkgconfig
criterion.pc

$ cat /nix/store/i3qwlyca06xx1fgdj1pcr4bzsyvfq4by-criterion-2.4.2-dev/lib/pkgconfig/criterion.pc
prefix=/nix/store/p0nnffxh9jrldxz5day2yvpvsa1pq6i3-criterion-2.4.2
includedir=/nix/store/i3qwlyca06xx1fgdj1pcr4bzsyvfq4by-criterion-2.4.2-dev/include
libdir=${prefix}/lib

Name: criterion
Description: A KISS, Cross platform unit testing framework for C and C++
URL: https://snai.pe/git/criterion
Version: 2.4.2
Requires.private: boxfort, libffi, libgit2, nanomsg
Libs: -Wl,-rpath,${libdir} -L${libdir} -lcriterion
Libs.private: -lintl /nix/store/m4k69gc780vwjha885782ipwa03qgyb2-nanopb-runtime-0.4.9.1/lib/libprotobuf-nanopb.a -lm /nix/store/m4k69gc780vwjha885782ipwa03qgyb2-nanopb-runtime-0.4.9.1/lib/libprotobuf-nanopb.a
Cflags: -I${includedir}

Project structure

- flake.nix
- CMakeLists.txt
- cmake
 | - FindCriterion.cmake
- include
 | - linked_list.h
- lib
 | - CMakeLists.txt
 | - linked_list.c
- tests
 | - CMakeLists.txt
 | - test_alloc_and_free.c

Files

./CMakeLists.txt

cmake_minimum_required(VERSION 3.31...4.0)

project(
  c-linked-list
  VERSION 1.0
  LANGUAGES C
  DESCRIPTION "A linked list implementation in C."
)

include(FetchContent)
include(CTest)

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")

enable_testing()
add_subdirectory("lib")
add_subdirectory("tests")

./cmake/FindCriterion.cmake

find_package(PkgConfig REQUIRED)
pkg_check_modules(PC_Criterion REQUIRED IMPORTED_TARGET criterion)

./tests/CMakeLists.txt

add_executable(test_alloc_and_free test_alloc_and_free.c)

find_package(Criterion REQUIRED)
# Tried explicitly including directorties, but that doesn't help.
# target_include_directories(test_alloc_and_free PUBLIC ${PC_Criterion_INCLUDE_DIRS})
# Tried changing from PRIVATE to PUBLIC when linking, but that doesn't help either.
# Also tried using the `FindCriterion.cmake` as specified in the Criterion examples, but that doesn't help either.
# https://github.com/Snaipe/Criterion/blob/bleeding/dev/cmake/cmake/FindCriterion.cmake
target_link_libraries(test_alloc_and_free PRIVATE c_linked_list PkgConfig::PC_Criterion)

add_test(NAME alloc_and_free COMMAND test_alloc_and_free)

./flake.nix

{
  inputs = {
    nixpkgs = {
      url = "github:NixOS/nixpkgs/release-25.05";
    };
    nixpkgs-unstable = {
      url = "github:NixOS/nixpkgs/nixpkgs-unstable";
    };
  };
  outputs = {
    self,
    nixpkgs,
    nixpkgs-unstable,
    ...
  }@inputs: let
    inherit (self) outputs;

    forAllSystems = nixpkgs.lib.genAttrs [
      "x86_64-linux"
      "aarch64-darwin"
    ];
  in {
    devShells = forAllSystems (
      system: let
        pkgs = nixpkgs.legacyPackages.${system};
        pkgs-unstable = nixpkgs-unstable.legacyPackages.${system};
      in {
        default = pkgs.mkShell {
          nativeBuildInputs = [
            pkgs.clang_20
            pkgs.lldb
            pkgs.cmake
            pkgs.pkg-config
          ];
          buildInputs = [
            # ===== Required by Criterion ===== #
            pkgs.boxfort.dev
            pkgs.libffi.dev
            pkgs.libgit2.dev
            pkgs.pcre2.dev
            pkgs.nanomsg
            # ===== Required by Criterion ===== #
            pkgs.criterion.dev
          ];
        };
      }
    );
  };
}

Correct link to Criterion: GitHub - Snaipe/Criterion: A cross-platform C and C++ unit testing framework for the 21st century. The original post links to the Haskell package by mistake, and I’m unable to edit it.

I was able to generate the correct -I flags by adding this to my ./cmake/FindCriterion.cmake

add_compile_options(${PC_Criterion_CFLAGS})

I was expecting that the cflags were also added when the IMPORTED_TARGET was linked (like the headers are included). But apparently not.