├── .bazelrc ├── .bazelversion ├── .clang-format ├── .clang-tidy ├── .github └── workflows │ └── main.yaml ├── .gitignore ├── .vscode ├── settings.json └── tasks.json ├── BUILD.bazel ├── LICENSE.txt ├── MODULE.bazel ├── README.md ├── bazel ├── BUILD.bazel ├── pandoc.BUILD ├── pkg_info.bzl └── version_h.bzl ├── bcc ├── BUILD.bazel ├── artifacts.cpp ├── artifacts.hpp ├── bazel.cpp ├── bazel.hpp ├── compile_commands.cpp ├── compile_commands.hpp ├── compile_commands_test.cpp ├── dep_set_of_files.cpp ├── dep_set_of_files.hpp ├── main.cpp ├── options.cpp ├── options.hpp ├── path_fragments.cpp ├── path_fragments.hpp ├── platform.cpp ├── platform.hpp ├── replacements.cpp ├── replacements.hpp └── replacements_test.cpp ├── copy.sh ├── documentation.md ├── tests ├── BUILD.bazel ├── bazel_mock.cpp └── self_test.cpp └── tools ├── archive.sh ├── clang-format ├── clang-tidy └── disk-cache-collect-garbage /.bazelrc: -------------------------------------------------------------------------------- 1 | # 2 | # .bazelrc 3 | # 4 | 5 | startup --windows_enable_symlinks 6 | 7 | common --verbose_failures 8 | common --incompatible_strict_action_env 9 | 10 | build --ui_event_filters=-info 11 | build --enable_runfiles 12 | 13 | build:cl --cxxopt=/std:c++17 14 | build:gnu --cxxopt=-std=c++17 15 | 16 | build:gnu --copt=-pedantic 17 | build:gnu --copt=-Wall 18 | build:gnu --copt=-Wextra 19 | 20 | build:release --compilation_mode=opt 21 | 22 | build:ci --show_timestamps 23 | build:ci --curses=no 24 | 25 | test --test_output=errors 26 | test --test_env=HOME 27 | 28 | # allow workspace specific user settings 29 | try-import %workspace%/user.bazelrc 30 | -------------------------------------------------------------------------------- /.bazelversion: -------------------------------------------------------------------------------- 1 | 8.2.1 2 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Mozilla 3 | ColumnLimit: 120 4 | QualifierAlignment: Right 5 | IncludeCategories: 6 | - Regex: '^((<|")boost/)' 7 | Priority: 4 8 | - Regex: '^((<|")(gtest|gmock)/)' 9 | Priority: 3 10 | - Regex: '<[[a-z_].]+>' 11 | Priority: 5 12 | - Regex: '"bcc/"' 13 | Priority: 2 14 | - Regex: '.*' 15 | Priority: 1 16 | ... 17 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: 'clang-diagnostic-*,clang-analyzer-*,-clang-analyzer-alpha*,-*,-modernize-*,readability-identifier-naming' 3 | WarningsAsErrors: '*' 4 | FormatStyle: 'file' 5 | HeaderFilterRegex: 'bcc/.*\.hpp' 6 | AnalyzeTemporaryDtors: false 7 | CheckOptions: 8 | - key: readability-identifier-naming.AbstractClassCase 9 | value: lower_case 10 | - key: readability-identifier-naming.AbstractClassPrefix 11 | value: '' 12 | - key: readability-identifier-naming.AbstractClassSuffix 13 | value: '' 14 | - key: readability-identifier-naming.ClassCase 15 | value: lower_case 16 | - key: readability-identifier-naming.ClassConstantCase 17 | value: lower_case 18 | - key: readability-identifier-naming.ClassConstantPrefix 19 | value: '' 20 | - key: readability-identifier-naming.ClassConstantSuffix 21 | value: '' 22 | - key: readability-identifier-naming.ClassMemberCase 23 | value: lower_case 24 | - key: readability-identifier-naming.ClassMemberPrefix 25 | value: '' 26 | - key: readability-identifier-naming.ClassMemberSuffix 27 | value: '' 28 | - key: readability-identifier-naming.ClassMethodCase 29 | value: lower_case 30 | - key: readability-identifier-naming.ClassMethodPrefix 31 | value: '' 32 | - key: readability-identifier-naming.ClassMethodSuffix 33 | value: '' 34 | - key: readability-identifier-naming.ClassPrefix 35 | value: '' 36 | - key: readability-identifier-naming.ClassSuffix 37 | value: '' 38 | - key: readability-identifier-naming.ConstantParameterCase 39 | value: lower_case 40 | - key: readability-identifier-naming.ConstantParameterPrefix 41 | value: '' 42 | - key: readability-identifier-naming.ConstantParameterSuffix 43 | value: '' 44 | - key: readability-identifier-naming.ConstantPrefix 45 | value: '' 46 | - key: readability-identifier-naming.ConstantSuffix 47 | value: '' 48 | - key: readability-identifier-naming.ConstexprFunctionCase 49 | value: lower_case 50 | - key: readability-identifier-naming.ConstexprFunctionPrefix 51 | value: '' 52 | - key: readability-identifier-naming.ConstexprFunctionSuffix 53 | value: '' 54 | - key: readability-identifier-naming.ConstexprMethodCase 55 | value: lower_case 56 | - key: readability-identifier-naming.ConstexprMethodPrefix 57 | value: '' 58 | - key: readability-identifier-naming.ConstexprMethodSuffix 59 | value: '' 60 | - key: readability-identifier-naming.ConstexprVariableCase 61 | value: lower_case 62 | - key: readability-identifier-naming.ConstexprVariablePrefix 63 | value: '' 64 | - key: readability-identifier-naming.ConstexprVariableSuffix 65 | value: '' 66 | - key: readability-identifier-naming.EnumCase 67 | value: lower_case 68 | - key: readability-identifier-naming.EnumConstantCase 69 | value: lower_case 70 | - key: readability-identifier-naming.EnumConstantPrefix 71 | value: '' 72 | - key: readability-identifier-naming.EnumConstantSuffix 73 | value: '' 74 | - key: readability-identifier-naming.EnumPrefix 75 | value: '' 76 | - key: readability-identifier-naming.EnumSuffix 77 | value: '' 78 | - key: readability-identifier-naming.FunctionCase 79 | value: lower_case 80 | - key: readability-identifier-naming.FunctionPrefix 81 | value: '' 82 | - key: readability-identifier-naming.FunctionSuffix 83 | value: '' 84 | - key: readability-identifier-naming.GlobalConstantCase 85 | value: lower_case 86 | - key: readability-identifier-naming.GlobalConstantPrefix 87 | value: '' 88 | - key: readability-identifier-naming.GlobalConstantSuffix 89 | value: '' 90 | - key: readability-identifier-naming.GlobalFunctionCase 91 | value: lower_case 92 | - key: readability-identifier-naming.GlobalFunctionPrefix 93 | value: '' 94 | - key: readability-identifier-naming.GlobalFunctionSuffix 95 | value: '' 96 | - key: readability-identifier-naming.GlobalVariableCase 97 | value: lower_case 98 | - key: readability-identifier-naming.GlobalVariablePrefix 99 | value: '' 100 | - key: readability-identifier-naming.GlobalVariableSuffix 101 | value: '' 102 | - key: readability-identifier-naming.IgnoreFailedSplit 103 | value: '0' 104 | - key: readability-identifier-naming.InlineNamespaceCase 105 | value: lower_case 106 | - key: readability-identifier-naming.InlineNamespacePrefix 107 | value: '' 108 | - key: readability-identifier-naming.InlineNamespaceSuffix 109 | value: '' 110 | - key: readability-identifier-naming.LocalConstantCase 111 | value: lower_case 112 | - key: readability-identifier-naming.LocalConstantPrefix 113 | value: '' 114 | - key: readability-identifier-naming.LocalConstantSuffix 115 | value: '' 116 | - key: readability-identifier-naming.LocalVariableCase 117 | value: lower_case 118 | - key: readability-identifier-naming.LocalVariablePrefix 119 | value: '' 120 | - key: readability-identifier-naming.LocalVariableSuffix 121 | value: '' 122 | - key: readability-identifier-naming.MemberCase 123 | value: lower_case 124 | - key: readability-identifier-naming.MemberPrefix 125 | value: '' 126 | - key: readability-identifier-naming.MemberSuffix 127 | value: '' 128 | - key: readability-identifier-naming.MethodCase 129 | value: lower_case 130 | - key: readability-identifier-naming.MethodPrefix 131 | value: '' 132 | - key: readability-identifier-naming.MethodSuffix 133 | value: '' 134 | - key: readability-identifier-naming.NamespaceCase 135 | value: lower_case 136 | - key: readability-identifier-naming.NamespacePrefix 137 | value: '' 138 | - key: readability-identifier-naming.NamespaceSuffix 139 | value: '' 140 | - key: readability-identifier-naming.ParameterCase 141 | value: lower_case 142 | - key: readability-identifier-naming.ParameterPackCase 143 | value: lower_case 144 | - key: readability-identifier-naming.ParameterPackPrefix 145 | value: '' 146 | - key: readability-identifier-naming.ParameterPackSuffix 147 | value: '' 148 | - key: readability-identifier-naming.ParameterPrefix 149 | value: '' 150 | - key: readability-identifier-naming.ParameterSuffix 151 | value: '' 152 | - key: readability-identifier-naming.PrivateMemberCase 153 | value: lower_case 154 | - key: readability-identifier-naming.PrivateMemberPrefix 155 | value: '' 156 | - key: readability-identifier-naming.PrivateMemberSuffix 157 | value: '_' 158 | - key: readability-identifier-naming.PrivateMethodCase 159 | value: lower_case 160 | - key: readability-identifier-naming.PrivateMethodPrefix 161 | value: '' 162 | - key: readability-identifier-naming.PrivateMethodSuffix 163 | value: '' 164 | - key: readability-identifier-naming.ProtectedMemberCase 165 | value: lower_case 166 | - key: readability-identifier-naming.ProtectedMemberPrefix 167 | value: '' 168 | - key: readability-identifier-naming.ProtectedMemberSuffix 169 | value: '_' 170 | - key: readability-identifier-naming.ProtectedMethodCase 171 | value: lower_case 172 | - key: readability-identifier-naming.ProtectedMethodPrefix 173 | value: '' 174 | - key: readability-identifier-naming.ProtectedMethodSuffix 175 | value: '' 176 | - key: readability-identifier-naming.PublicMemberCase 177 | value: lower_case 178 | - key: readability-identifier-naming.PublicMemberPrefix 179 | value: '' 180 | - key: readability-identifier-naming.PublicMemberSuffix 181 | value: '' 182 | - key: readability-identifier-naming.PublicMethodCase 183 | value: lower_case 184 | - key: readability-identifier-naming.PublicMethodPrefix 185 | value: '' 186 | - key: readability-identifier-naming.PublicMethodSuffix 187 | value: '' 188 | - key: readability-identifier-naming.StaticConstantCase 189 | value: lower_case 190 | - key: readability-identifier-naming.StaticConstantPrefix 191 | value: '' 192 | - key: readability-identifier-naming.StaticConstantSuffix 193 | value: '' 194 | - key: readability-identifier-naming.StaticVariableCase 195 | value: lower_case 196 | - key: readability-identifier-naming.StaticVariablePrefix 197 | value: '' 198 | - key: readability-identifier-naming.StaticVariableSuffix 199 | value: '' 200 | - key: readability-identifier-naming.StructCase 201 | value: lower_case 202 | - key: readability-identifier-naming.StructPrefix 203 | value: '' 204 | - key: readability-identifier-naming.StructSuffix 205 | value: '' 206 | - key: readability-identifier-naming.TemplateParameterCase 207 | value: lower_case 208 | - key: readability-identifier-naming.TemplateParameterPrefix 209 | value: '' 210 | - key: readability-identifier-naming.TemplateParameterSuffix 211 | value: '' 212 | - key: readability-identifier-naming.TemplateTemplateParameterCase 213 | value: lower_case 214 | - key: readability-identifier-naming.TemplateTemplateParameterPrefix 215 | value: '' 216 | - key: readability-identifier-naming.TemplateTemplateParameterSuffix 217 | value: '' 218 | - key: readability-identifier-naming.TypeTemplateParameterCase 219 | value: CamelCase 220 | - key: readability-identifier-naming.TypeTemplateParameterPrefix 221 | value: '' 222 | - key: readability-identifier-naming.TypeTemplateParameterSuffix 223 | value: '' 224 | - key: readability-identifier-naming.TypedefCase 225 | value: lower_case 226 | - key: readability-identifier-naming.TypedefPrefix 227 | value: '' 228 | - key: readability-identifier-naming.TypedefSuffix 229 | value: '' 230 | - key: readability-identifier-naming.UnionCase 231 | value: lower_case 232 | - key: readability-identifier-naming.UnionPrefix 233 | value: '' 234 | - key: readability-identifier-naming.UnionSuffix 235 | value: '' 236 | - key: readability-identifier-naming.ValueTemplateParameterCase 237 | value: lower_case 238 | - key: readability-identifier-naming.ValueTemplateParameterPrefix 239 | value: '' 240 | - key: readability-identifier-naming.ValueTemplateParameterSuffix 241 | value: '' 242 | - key: readability-identifier-naming.VariableCase 243 | value: lower_case 244 | - key: readability-identifier-naming.VariablePrefix 245 | value: '' 246 | - key: readability-identifier-naming.VariableSuffix 247 | value: '' 248 | - key: readability-identifier-naming.VirtualMethodCase 249 | value: lower_case 250 | - key: readability-identifier-naming.VirtualMethodPrefix 251 | value: '' 252 | - key: readability-identifier-naming.VirtualMethodSuffix 253 | value: '' 254 | ... 255 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | # GitHub Workflows file 2 | 3 | name: main 4 | 5 | on: 6 | push: 7 | branches: [main] 8 | pull_request: 9 | branches: [main] 10 | workflow_dispatch: 11 | inputs: 12 | version: 13 | description: "Version `x.y.z` (without leading `v`)" 14 | type: string 15 | required: true 16 | 17 | jobs: 18 | test: 19 | name: Build and test 20 | runs-on: ${{ matrix.os }} 21 | 22 | strategy: 23 | matrix: 24 | build: 25 | - linux 26 | # - windows 27 | include: 28 | - build: linux 29 | os: ubuntu-24.04 30 | config: gnu 31 | # - build: windows 32 | # os: windows-2019 33 | # config: cl 34 | 35 | steps: 36 | - name: Checkout repository 37 | uses: actions/checkout@v4 38 | - name: Bazel cache 39 | uses: actions/cache@v4 40 | with: 41 | path: | 42 | ~/.cache/bazel_build_cache 43 | key: ${{ runner.os }}-bazel-test-${{ hashFiles('MODULE.bazel') }} 44 | restore-keys: | 45 | ${{ runner.os }}-bazel-test- 46 | - name: Run bazel test 47 | run: bazel test --config=${{ matrix.config }} --config=ci --disk_cache=~/.cache/bazel_build_cache //... 48 | - name: Cache cleanup 49 | run: ./tools/disk-cache-collect-garbage ~/.cache/bazel_build_cache 50 | 51 | buildpkg: 52 | name: Build Release Asset 53 | runs-on: ${{ matrix.os }} 54 | needs: test 55 | 56 | strategy: 57 | matrix: 58 | build: 59 | - jammy 60 | - noble 61 | - macos-universal 62 | # - windows 63 | include: 64 | - build: jammy 65 | os: ubuntu-22.04 66 | config: gnu 67 | pkgfile: bazel-compile-commands_${{ github.event.inputs.version || 0 }}-jammy_amd64.deb 68 | - build: noble 69 | os: ubuntu-24.04 70 | config: gnu 71 | pkgfile: bazel-compile-commands_${{ github.event.inputs.version || 0 }}-noble_amd64.deb 72 | - build: macos-universal 73 | os: macos-latest 74 | config: gnu 75 | pkgfile: bazel-compile-commands_${{ github.event.inputs.version || 0 }}-macos_universal.zip 76 | # - build: windows 77 | # os: windows-2019 78 | # config: cl 79 | # pkgfile: bazel-compile-commands_${{ github.event.inputs.version || 0 }}-windows_amd64.zip 80 | 81 | steps: 82 | - name: Checkout repository 83 | uses: actions/checkout@v4 84 | - name: Bazel cache 85 | uses: actions/cache@v4 86 | with: 87 | path: | 88 | ~/.cache/bazel_build_cache 89 | key: ${{ matrix.os }}-bazel-buildpkg-${{ hashFiles('MODULE.bazel') }} 90 | restore-keys: | 91 | ${{ matrix.os }}-bazel-buildpkg- 92 | - name: Build Release Package 93 | run: > 94 | bazel run 95 | --config=${{ matrix.config }} 96 | --config=ci --config=release 97 | --//:version=${{ github.event.inputs.version || 0 }} 98 | --disk_cache=~/.cache/bazel_build_cache 99 | //:copy -- ${{ matrix.pkgfile }} 100 | - name: Upload package artifact 101 | uses: actions/upload-artifact@v4 102 | with: 103 | name: pkg-bazel-${{ matrix.build }} 104 | path: ${{ matrix.pkgfile }} 105 | - name: Cache cleanup 106 | run: ./tools/disk-cache-collect-garbage ~/.cache/bazel_build_cache 107 | 108 | self_test: 109 | name: Self test 110 | runs-on: ubuntu-24.04 111 | needs: buildpkg 112 | steps: 113 | - name: Checkout repository 114 | uses: actions/checkout@v4 115 | - name: Bazel cache 116 | uses: actions/cache@v4 117 | with: 118 | path: | 119 | ~/.cache/bazel_build_cache 120 | key: bazel-selftest-${{ hashFiles('MODUE.bazel') }} 121 | restore-keys: | 122 | bazel-selftest- 123 | - name: Run bazel to generate worktree 124 | run: bazel build --config=gnu --config=ci //... 125 | - name: Download pkg artifact 126 | uses: actions/download-artifact@v4 127 | with: 128 | pattern: pkg-bazel-noble 129 | - name: Install 130 | run: | 131 | sudo dpkg --install pkg-bazel-noble/bazel-compile-commands_*.deb 132 | - name: Create compile-commands.json 133 | run: | 134 | /usr/bin/bazel-compile-commands -v -a --config=gnu --replace=-fno-canonical-system-headers= //... 135 | - name: Run clang-tidy 136 | run: | 137 | clang-tidy -p "$(pwd)" $(find . -type f -name '*.cpp') 138 | 139 | mkrelease: 140 | name: Create Release 141 | runs-on: ubuntu-latest 142 | needs: 143 | - buildpkg 144 | if: github.event_name == 'workflow_dispatch' 145 | 146 | steps: 147 | - name: Checkout repository 148 | uses: actions/checkout@v4 149 | - name: Download pkg artifact 150 | uses: actions/download-artifact@v4 151 | with: 152 | pattern: pkg-* 153 | merge-multiple: true 154 | # - name: Create Source Archive 155 | # run: | 156 | # ./tools/archive.sh 157 | # env: 158 | # TAG: v${{ github.event.inputs.version }} 159 | - name: Create Release 160 | run: | 161 | gh release create v${{ github.event.inputs.version }} --generate-notes *.deb *.zip 162 | env: 163 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 164 | 165 | format_check: 166 | name: Style check 167 | runs-on: ubuntu-latest 168 | if: github.event_name == 'pull_request' 169 | 170 | steps: 171 | - name: Checkout repository 172 | uses: actions/checkout@v4 173 | with: 174 | fetch-depth: 1 175 | - name: Install LLVM 176 | uses: KyleMayes/install-llvm-action@v1.6.1 177 | with: 178 | version: "15.0.6" 179 | - name: clang-format --version 180 | run: clang-format --version 181 | - name: Style check 182 | run: ./tools/clang-format --dry-run -Werror 183 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | MODULE.bazel.lock 2 | bazel-* 3 | bazel-compile-commands-*.tar.gz 4 | user.bazelrc 5 | .bazelccrc 6 | compile_flags.txt 7 | compile_commands.json 8 | rust-project.json 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "__bit_reference": "cpp", 4 | "__hash_table": "cpp", 5 | "__locale": "cpp", 6 | "__node_handle": "cpp", 7 | "__split_buffer": "cpp", 8 | "__threading_support": "cpp", 9 | "__verbose_abort": "cpp", 10 | "array": "cpp", 11 | "bitset": "cpp", 12 | "cctype": "cpp", 13 | "clocale": "cpp", 14 | "cmath": "cpp", 15 | "complex": "cpp", 16 | "cstdarg": "cpp", 17 | "cstddef": "cpp", 18 | "cstdint": "cpp", 19 | "cstdio": "cpp", 20 | "cstdlib": "cpp", 21 | "cstring": "cpp", 22 | "ctime": "cpp", 23 | "cwchar": "cpp", 24 | "cwctype": "cpp", 25 | "deque": "cpp", 26 | "execution": "cpp", 27 | "memory": "cpp", 28 | "fstream": "cpp", 29 | "initializer_list": "cpp", 30 | "iomanip": "cpp", 31 | "ios": "cpp", 32 | "iosfwd": "cpp", 33 | "iostream": "cpp", 34 | "istream": "cpp", 35 | "limits": "cpp", 36 | "locale": "cpp", 37 | "mutex": "cpp", 38 | "new": "cpp", 39 | "optional": "cpp", 40 | "ostream": "cpp", 41 | "print": "cpp", 42 | "queue": "cpp", 43 | "ratio": "cpp", 44 | "sstream": "cpp", 45 | "stack": "cpp", 46 | "stdexcept": "cpp", 47 | "streambuf": "cpp", 48 | "string": "cpp", 49 | "string_view": "cpp", 50 | "tuple": "cpp", 51 | "typeinfo": "cpp", 52 | "unordered_map": "cpp", 53 | "variant": "cpp", 54 | "vector": "cpp", 55 | "algorithm": "cpp" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Run bazel-compile-commands", 6 | "command": "bazel-compile-commands", 7 | "type": "shell", 8 | "isBackground": true, 9 | "hide": true 10 | }, 11 | { 12 | "label": "Update compile_commands.json", 13 | "command": "${command:clangd.restart}", 14 | "isBackground": true, 15 | "dependsOn": [ 16 | "Run bazel-compile-commands" 17 | ], 18 | "group": { 19 | "kind": "build", 20 | "isDefault": true 21 | } 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@bazel_skylib//rules:common_settings.bzl", "string_flag") 2 | load("@rules_pkg//:mappings.bzl", "pkg_attributes", "pkg_filegroup", "pkg_files") 3 | load("@rules_pkg//:pkg.bzl", "pkg_deb", "pkg_tar", "pkg_zip") 4 | load("//bazel:pkg_info.bzl", "pkg_variables", "pkg_version") 5 | 6 | package(default_visibility = ["//visibility:public"]) 7 | 8 | platform( 9 | name = "arm64-apple-darwin", 10 | constraint_values = [ 11 | "@platforms//cpu:arm64", 12 | "@platforms//os:macos", 13 | ], 14 | ) 15 | 16 | platform( 17 | name = "x86_64-apple-darwin", 18 | constraint_values = [ 19 | "@platforms//cpu:x86_64", 20 | "@platforms//os:macos", 21 | ], 22 | ) 23 | 24 | filegroup( 25 | name = "pandoc_macos", 26 | srcs = select({ 27 | "@platforms//cpu:arm64": ["@pandoc_macos_arm64//:pandoc"], 28 | "@platforms//cpu:x86_64": ["@pandoc_macos_x86_64//:pandoc"], 29 | }), 30 | ) 31 | 32 | filegroup( 33 | name = "pandoc", 34 | srcs = select({ 35 | "@platforms//os:linux": ["@pandoc_linux_x86_64//:pandoc"], 36 | "@platforms//os:macos": [":pandoc_macos"], 37 | "@platforms//os:windows": ["@pandoc_windowss_x86_64//:pandoc"], 38 | }), 39 | ) 40 | 41 | genrule( 42 | name = "man", 43 | srcs = ["documentation.md"], 44 | outs = ["bazel-compile-commands.1"], 45 | cmd = "$(location :pandoc) -f markdown -t man -o $@ $(location documentation.md)", 46 | tools = [":pandoc"], 47 | ) 48 | 49 | pkg_files( 50 | name = "bin_files", 51 | srcs = [ 52 | "//bcc:bazel-compile-commands-bin", 53 | ], 54 | attributes = pkg_attributes(mode = "0755"), 55 | prefix = "bin", 56 | ) 57 | 58 | pkg_files( 59 | name = "man_files", 60 | srcs = [":man"], 61 | prefix = "share/man/man1", 62 | ) 63 | 64 | pkg_filegroup( 65 | name = "usr_files", 66 | srcs = [ 67 | ":bin_files", 68 | ":man_files", 69 | ], 70 | prefix = "/usr", 71 | ) 72 | 73 | pkg_deb( 74 | name = "deb", 75 | architecture = select({ 76 | "@platforms//cpu:aarch64": "arm64", 77 | "@platforms//cpu:x86_64": "amd64", 78 | }), 79 | built_using = "unzip (6.0.1)", 80 | data = ":tar", 81 | description = "Generates compile_commands.json file from a Bazel workspace", 82 | homepage = "https://github.com/kiron1/bazel-compile-commands", 83 | maintainer = "Kiron ", 84 | package = "bazel-compile-commands", 85 | package_file_name = "bazel-compile-commands_{version}_{architecture}.deb", 86 | package_variables = "@//:variables", 87 | version_file = "@//:version_file", 88 | ) 89 | 90 | pkg_tar( 91 | name = "tar", 92 | srcs = [":usr_files"], 93 | ) 94 | 95 | pkg_zip( 96 | name = "zip", 97 | srcs = [":usr_files"], 98 | package_file_name = select({ 99 | "@platforms//os:macos": "bazel-compile-commands_{version}_{os}_universal.zip", 100 | "//conditions:default": "bazel-compile-commands_{version}_{os}_{architecture}.zip", 101 | }), 102 | package_variables = "@//:variables", 103 | ) 104 | 105 | alias( 106 | name = "pkg", 107 | actual = select({ 108 | "@platforms//os:linux": ":deb", 109 | # Build a zip file for any other platforms: 110 | "//conditions:default": ":zip", 111 | }), 112 | ) 113 | 114 | sh_binary( 115 | name = "copy", 116 | srcs = ["copy.sh"], 117 | data = [":pkg"], 118 | env = {"PKG": "$(location :pkg)"}, 119 | deps = ["@bazel_tools//tools/bash/runfiles"], 120 | ) 121 | 122 | pkg_variables( 123 | name = "variables", 124 | architecture = select({ 125 | "@platforms//cpu:x86_64": "amd64", 126 | "@platforms//cpu:arm64": "arm64", 127 | }), 128 | os = select({ 129 | "@platforms//os:linux": "linux", 130 | "@platforms//os:macos": "macos", 131 | "@platforms//os:windows": "windows", 132 | "//conditions:default": "unknown", 133 | }), 134 | version = "//:version", 135 | ) 136 | 137 | pkg_version( 138 | name = "version_file", 139 | out = "version.txt", 140 | version = "//:version", 141 | ) 142 | 143 | string_flag( 144 | name = "version", 145 | build_setting_default = "0", 146 | visibility = ["//visibility:public"], 147 | ) 148 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Bazel Compile Commands License 2 | ============================== 3 | 4 | Copyright 2022 Kiron 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 | of the Software, and to permit persons to whom the Software is furnished to do 11 | so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | --- 25 | 26 | https://opensource.org/licenses/MIT 27 | -------------------------------------------------------------------------------- /MODULE.bazel: -------------------------------------------------------------------------------- 1 | module( 2 | name = "bazel-compile-commands", 3 | version = "0.17.2", 4 | compatibility_level = 0, 5 | ) 6 | 7 | bazel_dep(name = "apple_support", version = "1.21.1", repo_name = "build_bazel_apple_support") 8 | bazel_dep(name = "bazel_skylib", version = "1.7.1") 9 | bazel_dep(name = "boost.core", version = "1.87.0") 10 | bazel_dep(name = "boost.io", version = "1.87.0") 11 | bazel_dep(name = "boost.json", version = "1.87.0") 12 | bazel_dep(name = "boost.process", version = "1.87.0") 13 | bazel_dep(name = "boost.program_options", version = "1.87.0") 14 | bazel_dep(name = "googleapis", version = "0.0.0-20241220-5e258e33.bcr.1") 15 | bazel_dep(name = "platforms", version = "0.0.11") 16 | bazel_dep(name = "protobuf", version = "30.2", repo_name = "com_google_protobuf") 17 | bazel_dep(name = "rules_apple", version = "3.20.1", repo_name = "build_bazel_rules_apple") 18 | bazel_dep(name = "rules_cc", version = "0.1.1") 19 | bazel_dep(name = "rules_java", version = "8.11.0") 20 | bazel_dep(name = "rules_pkg", version = "1.1.0") 21 | bazel_dep(name = "rules_python", version = "1.3.0") 22 | 23 | bazel_dep(name = "googletest", version = "1.16.0", dev_dependency = True, repo_name = "com_google_googletest") 24 | 25 | apple_cc_configure = use_extension( 26 | "@build_bazel_apple_support//crosstool:setup.bzl", 27 | "apple_cc_configure_extension", 28 | ) 29 | use_repo(apple_cc_configure, "local_config_apple_cc") 30 | 31 | http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 32 | 33 | http_archive( 34 | name = "io_bazel", 35 | integrity = "sha256-T/Rg0FAsA7Wcdy9zjXdLedbAWaPVDq+MDnczfdh0m9s=", 36 | strip_prefix = "bazel-8.0.0", 37 | urls = [ 38 | "https://github.com/bazelbuild/bazel/archive/refs/tags/8.0.0.tar.gz", 39 | ], 40 | ) 41 | 42 | http_archive( 43 | name = "pandoc_linux_x86_64", 44 | build_file = "//bazel:pandoc.BUILD", 45 | integrity = "sha256-jjcCsZX3VBLkJd9G+PPwgkG2aiszq72eBO2lAb/ehgw=", 46 | strip_prefix = "pandoc-3.6", 47 | urls = ["https://github.com/jgm/pandoc/releases/download/3.6/pandoc-3.6-linux-amd64.tar.gz"], 48 | ) 49 | 50 | http_archive( 51 | name = "pandoc_macos_x86_64", 52 | build_file = "//bazel:pandoc.BUILD", 53 | integrity = "sha256-I6KEyo+ibG07XtMkmlu4K1kpuexjYzxMFW3Lv3V67e8=", 54 | strip_prefix = "pandoc-3.6-x86_64", 55 | urls = ["https://github.com/jgm/pandoc/releases/download/3.6/pandoc-3.6-x86_64-macOS.zip"], 56 | ) 57 | 58 | http_archive( 59 | name = "pandoc_macos_arm64", 60 | build_file = "//bazel:pandoc.BUILD", 61 | integrity = "sha256-eP7a5CZoLym9PA2uUfdyWXOvvAEVM7NYAmVw6lkPx4U=", 62 | strip_prefix = "pandoc-3.6-arm64", 63 | urls = ["https://github.com/jgm/pandoc/releases/download/3.6/pandoc-3.6-arm64-macOS.zip"], 64 | ) 65 | 66 | http_archive( 67 | name = "pandoc_windows_x86_64", 68 | build_file = "//bazel:pandoc.BUILD", 69 | integrity = "sha256-BnMre5bTuZ9xG6G/JPKJ/LW8lWEZK9wb2VFMF8iqLCA=", 70 | strip_prefix = "pandoc-3.6", 71 | urls = ["https://github.com/jgm/pandoc/releases/download/3.6/pandoc-3.6-windows-x86_64.zip"], 72 | ) 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `compile_commands.json` generator for Bazel 2 | 3 | ![bazel-compile-commands build status of main branch](https://github.com/kiron1/bazel-compile-commands/actions/workflows/main.yaml/badge.svg) 4 | 5 | This repository contains two tools to generate a `compile_commands.json` file 6 | from a Bazel workspace without the need to modify any of the Bazel configuration 7 | files. This is the equivalent of _CMAKE_EXPORT_COMPILE_COMMANDS_ from CMake for 8 | Bazel. 9 | 10 | The 11 | [Microsoft C/C++ Extension for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) 12 | struggles sometimes with Bazel workspaces since not all include paths are at the 13 | default locations. The `compile_commands.json` file can be used with `clangd` to 14 | have a significant better LSP experience. For 15 | [Visual Studio Code (VS Code)](https://code.visualstudio.com), the 16 | [clangd extension](https://marketplace.visualstudio.com/items?itemName=llvm-vs-code-extensions.vscode-clangd) 17 | can be used to utilize the project compile information via the 18 | `compile_commands.json` file. 19 | 20 | Invoke `bazel-compile-commands //...` to generate a `compile_commands.json` file 21 | for the 22 | [Bazel label](https://docs.bazel.build/versions/3.1.0/build-ref.html#labels) 23 | `//...`. For more information, see 24 | [`man 1 bazel-compile-commands`](./documentation.md). 25 | 26 | The `bazel-compile-commands` command is ideal when you need a 27 | `compile_commands.json` file instantly and don't require automatic updates of 28 | the `compile_commands.json` file. For example in CI use cases when you when 29 | static analysis tools use a `compile_commands.json` file as input. 30 | 31 | ## Usage of `bazel-compile-commands` 32 | 33 | Inside a Bazel workspace run: 34 | 35 | ```sh 36 | bazel-compile-commands 37 | ``` 38 | 39 | This will generate a `compile_commands.json` file in the current directory. 40 | 41 | - [Documentation](./documentation.md) or `man 1 bazel-compile-commands` 42 | - [Download latest release](https://github.com/kiron1/bazel-compile-commands/releases/latest) 43 | 44 | ### VS Code integration 45 | 46 | See the [`.vscode/tasks.json`](./.vscode/tasks.json) file as an example on how 47 | to integrate`bazel-compile-commands` into VS Code. 48 | 49 | ## Mentions 50 | 51 | - [Bartek Kryza mentions `bazel-compile-commands` in the blog post 52 | "compile_commands.json gallery"](https://blog.bkryza.com/posts/compile-commands-json-gallery/), 53 | _Dec. 2, 2004_. 54 | - [Blog post in System/5: BazelCon 2024 recap, section IDE support, bullet point 55 | "Compilation database"](https://blogsystem5.substack.com/p/bazelcon-2024-recap), 56 | _Oct. 22, 2024_. 57 | - [BazelCon 2024: The State of Compilation Database in Bazel](https://www.youtube.com/watch?v=HJGD0-mX6G8&list=PLbzoR-pLrL6ptKfAQNZ5RS4HMdmeilBcw&index=41) 58 | (Video) 59 | 60 | ## Build 61 | 62 | ### Linux and macOS 63 | 64 | ```sh 65 | bazel build --config=gnu //bcc:bazel-compile-commands 66 | ``` 67 | 68 | ### Windows 69 | 70 | ```sh 71 | bazel build --config=cl //bcc:bazel-compile-commands 72 | ``` 73 | 74 | ## Alternative tools 75 | 76 | - [Bear](https://github.com/rizsotto/Bear) - Can work when used with the 77 | [`--spawn_strategy=local`](https://docs.bazel.build/versions/main/user-manual.html#flag--spawn_strategy) 78 | Bazel flag. 79 | - [hedronvision/bazel-compile-commands-extractor](https://github.com/hedronvision/bazel-compile-commands-extractor) - 80 | Can be integrated into Bazel files. 81 | 82 | ## Links 83 | 84 | - [Format of the `compile_commands.json` format](https://clang.llvm.org/docs/JSONCompilationDatabase.html) 85 | - [clangd](https://clangd.llvm.org/) 86 | - [analysis_v2.proto](https://github.com/bazelbuild/bazel/blob/master/src/main/protobuf/analysis_v2.proto) 87 | - [Build Event Protocol](https://bazel.build/remote/bep) 88 | 89 | ## Issues 90 | 91 | - https://github.com/bazelbuild/bazel/issues/12852 92 | 93 | ## License 94 | 95 | This source code is under the [MIT](https://opensource.org/licenses/MIT) license 96 | with the exceptions mentioned in "Third party source code in this repository". 97 | -------------------------------------------------------------------------------- /bazel/BUILD.bazel: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kiron1/bazel-compile-commands/0ee8fcdb00aee22a98d75a71a623a70a8f05f6e0/bazel/BUILD.bazel -------------------------------------------------------------------------------- /bazel/pandoc.BUILD: -------------------------------------------------------------------------------- 1 | filegroup( 2 | name = "pandoc", 3 | srcs = [ 4 | "bin/pandoc", 5 | ], 6 | visibility = ["//visibility:public"], 7 | ) 8 | -------------------------------------------------------------------------------- /bazel/pkg_info.bzl: -------------------------------------------------------------------------------- 1 | load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo") 2 | load("@rules_pkg//pkg:providers.bzl", "PackageVariablesInfo") 3 | 4 | def _pkg_variables_impl(ctx): 5 | values = { 6 | "architecture": ctx.attr.architecture, 7 | "os": ctx.attr.os, 8 | "version": ctx.attr.version[BuildSettingInfo].value, 9 | } 10 | return PackageVariablesInfo(values = values) 11 | 12 | pkg_variables = rule( 13 | implementation = _pkg_variables_impl, 14 | attrs = { 15 | "architecture": attr.string( 16 | doc = "Architecture of this build.", 17 | ), 18 | "os": attr.string( 19 | doc = "OS of this build.", 20 | ), 21 | "version": attr.label( 22 | doc = "Version of this build.", 23 | ), 24 | }, 25 | doc = "Collect variables used during package generation.", 26 | ) 27 | 28 | def _pkg_version_impl(ctx): 29 | ctx.actions.write( 30 | output = ctx.outputs.out, 31 | content = "{}\n".format(ctx.attr.version[BuildSettingInfo].value), 32 | ) 33 | files = depset(direct = [ctx.outputs.out]) 34 | runfiles = ctx.runfiles(files = [ctx.outputs.out]) 35 | return [DefaultInfo(files = files, data_runfiles = runfiles)] 36 | 37 | pkg_version = rule( 38 | implementation = _pkg_version_impl, 39 | attrs = { 40 | "out": attr.output(mandatory = True), 41 | "version": attr.label( 42 | doc = "Version of this build.", 43 | ), 44 | }, 45 | doc = "Version as file.", 46 | ) 47 | -------------------------------------------------------------------------------- /bazel/version_h.bzl: -------------------------------------------------------------------------------- 1 | load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo") 2 | 3 | def _version_h_impl(ctx): 4 | guard = "{}_{}_H_INCLUDED".format(ctx.label.name.upper(), ctx.attr.varname) 5 | content = [ 6 | "#ifndef {}".format(guard), 7 | "#define {} \"{}\"".format(ctx.attr.varname, ctx.attr.version[BuildSettingInfo].value), 8 | "#endif", 9 | ] 10 | ctx.actions.write( 11 | output = ctx.outputs.out, 12 | content = "\n".join(content), 13 | ) 14 | files = depset(direct = [ctx.outputs.out]) 15 | runfiles = ctx.runfiles(files = [ctx.outputs.out]) 16 | return [DefaultInfo(files = files, data_runfiles = runfiles)] 17 | 18 | version_h = rule( 19 | implementation = _version_h_impl, 20 | output_to_genfiles = True, 21 | attrs = { 22 | "out": attr.output(mandatory = True), 23 | "varname": attr.string( 24 | doc = "Name of the varible to hold the versions", 25 | ), 26 | "version": attr.label( 27 | doc = "Version of this build.", 28 | ), 29 | }, 30 | doc = "Version as C/C++ header file.", 31 | ) 32 | -------------------------------------------------------------------------------- /bcc/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@bazel_skylib//rules:copy_file.bzl", "copy_file") 2 | load("@build_bazel_apple_support//rules:universal_binary.bzl", "universal_binary") 3 | load("//bazel:version_h.bzl", "version_h") 4 | 5 | package(default_visibility = ["//visibility:public"]) 6 | 7 | cc_binary( 8 | name = "bazel-compile-commands-binary", 9 | srcs = [ 10 | "main.cpp", 11 | ], 12 | linkopts = select({ 13 | "@platforms//os:windows": ["-DEFAULTLIB:shell32.lib"], 14 | "//conditions:default": [], 15 | }), 16 | deps = [ 17 | ":bazel", 18 | ":compile_commands", 19 | ":options", 20 | ":platform", 21 | ":replacements", 22 | ], 23 | ) 24 | 25 | universal_binary( 26 | name = "bazel-compile-commands-universal", 27 | binary = ":bazel-compile-commands-binary", 28 | target_compatible_with = ["@platforms//os:macos"], 29 | ) 30 | 31 | alias( 32 | name = "bazel-compile-commands-alias", 33 | actual = 34 | select({ 35 | "@platforms//os:macos": ":bazel-compile-commands-universal", 36 | "//conditions:default": ":bazel-compile-commands-binary", 37 | }), 38 | ) 39 | 40 | copy_file( 41 | name = "bazel-compile-commands-bin", 42 | src = ":bazel-compile-commands-alias", 43 | out = "bazel-compile-commands", 44 | allow_symlink = False, 45 | is_executable = True, 46 | ) 47 | 48 | cc_proto_library( 49 | name = "analysis", 50 | deps = ["@io_bazel//src/main/protobuf:analysis_v2_proto"], 51 | ) 52 | 53 | cc_library( 54 | name = "artifacts", 55 | srcs = ["artifacts.cpp"], 56 | hdrs = ["artifacts.hpp"], 57 | deps = [ 58 | ":analysis", 59 | ":path_fragments", 60 | ], 61 | ) 62 | 63 | cc_library( 64 | name = "bazel", 65 | srcs = ["bazel.cpp"], 66 | hdrs = ["bazel.hpp"], 67 | defines = ["BOOST_PROCESS_USE_STD_FS=1"], 68 | deps = [ 69 | ":analysis", 70 | "@boost.json", 71 | "@boost.process", 72 | ], 73 | ) 74 | 75 | cc_library( 76 | name = "compile_commands", 77 | srcs = ["compile_commands.cpp"], 78 | hdrs = ["compile_commands.hpp"], 79 | deps = [ 80 | ":analysis", 81 | ":artifacts", 82 | ":dep_set_of_files", 83 | ":path_fragments", 84 | ":replacements", 85 | "@boost.io", 86 | "@boost.json", 87 | ], 88 | ) 89 | 90 | cc_test( 91 | name = "compile_commands_test", 92 | size = "small", 93 | srcs = ["compile_commands_test.cpp"], 94 | deps = [ 95 | ":compile_commands", 96 | "@com_google_googletest//:gtest_main", 97 | ], 98 | ) 99 | 100 | cc_library( 101 | name = "dep_set_of_files", 102 | srcs = ["dep_set_of_files.cpp"], 103 | hdrs = ["dep_set_of_files.hpp"], 104 | deps = [ 105 | ":analysis", 106 | ":artifacts", 107 | "@boost.core", 108 | ], 109 | ) 110 | 111 | cc_library( 112 | name = "options", 113 | srcs = ["options.cpp"], 114 | hdrs = [ 115 | "options.hpp", 116 | ":version", 117 | ], 118 | defines = ["BOOST_PROCESS_USE_STD_FS=1"], 119 | deps = [ 120 | "@boost.process", 121 | "@boost.program_options", 122 | ], 123 | ) 124 | 125 | cc_library( 126 | name = "path_fragments", 127 | srcs = ["path_fragments.cpp"], 128 | hdrs = ["path_fragments.hpp"], 129 | deps = [ 130 | ":analysis", 131 | ], 132 | ) 133 | 134 | cc_library( 135 | name = "platform", 136 | srcs = ["platform.cpp"], 137 | hdrs = ["platform.hpp"], 138 | defines = ["BOOST_PROCESS_USE_STD_FS=1"], 139 | deps = [ 140 | ":replacements", 141 | "@boost.process", 142 | ], 143 | ) 144 | 145 | cc_library( 146 | name = "replacements", 147 | srcs = ["replacements.cpp"], 148 | hdrs = ["replacements.hpp"], 149 | ) 150 | 151 | version_h( 152 | name = "version", 153 | out = "version.h", 154 | varname = "BCC_VERSION", 155 | version = "//:version", 156 | ) 157 | 158 | cc_test( 159 | name = "replacements_test", 160 | size = "small", 161 | srcs = ["replacements_test.cpp"], 162 | deps = [ 163 | ":replacements", 164 | "@com_google_googletest//:gtest_main", 165 | ], 166 | ) 167 | -------------------------------------------------------------------------------- /bcc/artifacts.cpp: -------------------------------------------------------------------------------- 1 | #include "bcc/artifacts.hpp" 2 | 3 | namespace bcc { 4 | 5 | artifacts::artifacts(google::protobuf::RepeatedPtrField const& artifacts, path_fragments fragments) 6 | : fragments_(std::move(fragments)) 7 | { 8 | for (auto const& k : artifacts) { 9 | artifacts_.insert({ k.id(), k.path_fragment_id() }); 10 | } 11 | } 12 | 13 | std::string 14 | artifacts::path_of_artifact(std::uint32_t artifact_id) const 15 | { 16 | std::string result; 17 | if (auto const ait = artifacts_.find(artifact_id); ait != artifacts_.end()) { 18 | result = fragments_.build(ait->second); 19 | } 20 | return result; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /bcc/artifacts.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "src/main/protobuf/analysis_v2.pb.h" 7 | 8 | #include "bcc/path_fragments.hpp" 9 | 10 | namespace bcc { 11 | /// Artefacts for lookup. 12 | class artifacts 13 | { 14 | public: 15 | artifacts(google::protobuf::RepeatedPtrField const& artifacts, path_fragments fragments); 16 | 17 | std::string path_of_artifact(std::uint32_t artifact_id) const; 18 | 19 | private: 20 | std::unordered_map artifacts_; 21 | path_fragments fragments_; 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /bcc/bazel.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include "bcc/bazel.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | namespace bcc { 19 | 20 | namespace { 21 | /// Run `bazel info $location` and return it as path 22 | std::filesystem::path 23 | bazel_info(std::filesystem::path const& bazel_path, 24 | std::vector const& bazel_startup_options, 25 | std::string const& location) 26 | { 27 | std::vector args(std::begin(bazel_startup_options), std::end(bazel_startup_options)); 28 | args.push_back("info"); 29 | args.push_back(location); 30 | boost::process::ipstream outs; 31 | boost::process::ipstream errs; 32 | boost::process::child bazel_proc( 33 | bazel_path, boost::process::args(args), boost::process::std_out > outs, boost::process::std_err > errs); 34 | 35 | auto line = std::string{}; 36 | std::getline(outs, line); 37 | bazel_proc.wait(); 38 | auto const rc = bazel_proc.exit_code(); 39 | if (rc != 0) { 40 | std::ostringstream oss; 41 | oss << errs.rdbuf(); 42 | throw bazel_error(bazel_path, args, rc, oss.str()); 43 | } 44 | return std::filesystem::path(line); 45 | } 46 | 47 | std::string 48 | make_bazel_error_message(std::filesystem::path const& path, std::vector args, int rc, std::string error) 49 | { 50 | std::stringstream msg; 51 | msg << "bazel command failed with exit code " << rc << ": " << path.string() << " "; 52 | std::copy(std::begin(args), std::end(args), std::ostream_iterator(msg, " ")); 53 | msg << ": " << error; 54 | return msg.str(); 55 | } 56 | } 57 | 58 | bazel_error::bazel_error(std::filesystem::path const& path, std::vector args, int rc, std::string error) 59 | : std::runtime_error(make_bazel_error_message(path, args, rc, error)) 60 | { 61 | } 62 | 63 | workspace_error::workspace_error() 64 | : std::logic_error("workspace is invalid") 65 | { 66 | } 67 | proto_error::proto_error() 68 | : std::runtime_error("JSON document is invalid") 69 | { 70 | } 71 | proto_error::proto_error(std::string const& what) 72 | : std::runtime_error(what) 73 | { 74 | } 75 | 76 | bazel 77 | bazel::create(std::filesystem::path const& bazel_path, std::vector bazel_startup_options) 78 | { 79 | auto workspace = bazel_info(bazel_path, bazel_startup_options, "workspace"); 80 | auto execution_root = bazel_info(bazel_path, bazel_startup_options, "execution_root"); 81 | 82 | if (workspace.empty()) { 83 | throw workspace_error(); 84 | } 85 | 86 | return bazel(bazel_path, std::move(bazel_startup_options), workspace, execution_root); 87 | } 88 | 89 | analysis::ActionGraphContainer 90 | bazel::aquery(std::string const& query, 91 | std::vector const& bazel_flags, 92 | std::vector const& configs) const 93 | { 94 | std::vector args(std::begin(bazel_startup_options_), std::end(bazel_startup_options_)); 95 | args.push_back("aquery"); 96 | args.push_back("--output=proto"); 97 | args.push_back("--ui_event_filters=-info"); 98 | args.push_back("--noshow_progress"); 99 | std::copy(std::begin(bazel_flags), std::end(bazel_flags), std::back_inserter(args)); 100 | std::transform(std::begin(configs), std::end(configs), std::back_inserter(args), [&](auto c) { 101 | return std::string("--config=") + c; 102 | }); 103 | args.push_back(query); 104 | 105 | boost::process::ipstream outs; 106 | boost::process::child bazel_proc(bazel_command_, boost::process::args(args), boost::process::std_out > outs); 107 | 108 | analysis::ActionGraphContainer agc; 109 | if (!agc.ParseFromIstream(&outs)) { 110 | throw proto_error("failed to parse aquery output"); 111 | } 112 | bazel_proc.wait(); 113 | auto const rc = bazel_proc.exit_code(); 114 | if (rc != 0) { 115 | throw bazel_error(bazel_command_, args, rc, std::string()); 116 | } 117 | return agc; 118 | } 119 | 120 | bazel::bazel(std::filesystem::path bazel_commands, 121 | std::vector bazel_startup_options, 122 | std::filesystem::path workspace_path, 123 | std::filesystem::path execution_root) 124 | : bazel_command_(std::move(bazel_commands)) 125 | , bazel_startup_options_(std::move(bazel_startup_options)) 126 | , workspace_path_(std::move(workspace_path)) 127 | , execution_root_(std::move(execution_root)) 128 | { 129 | } 130 | } // namespace bcc 131 | -------------------------------------------------------------------------------- /bcc/bazel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include "src/main/protobuf/analysis_v2.pb.h" 11 | 12 | namespace bcc { 13 | 14 | class bazel_error : public std::runtime_error 15 | { 16 | public: 17 | bazel_error(std::filesystem::path const& path, std::vector args, int rc, std::string error); 18 | }; 19 | 20 | class workspace_error : public std::logic_error 21 | { 22 | public: 23 | workspace_error(); 24 | }; 25 | 26 | class proto_error : public std::runtime_error 27 | { 28 | public: 29 | proto_error(); 30 | proto_error(std::string const& what); 31 | }; 32 | 33 | class bazel 34 | { 35 | public: 36 | static bazel create(std::filesystem::path const& bazel_path, std::vector bazel_startup_options); 37 | 38 | // Path of the `bazel` executable. 39 | std::filesystem::path command_path() const { return bazel_command_; }; 40 | 41 | // Return the path to the current workspace 42 | std::filesystem::path workspace_path() const { return workspace_path_; }; 43 | 44 | // Return the path to the execution root of bazel 45 | std::filesystem::path execution_root() const { return execution_root_; }; 46 | 47 | // Execute an `aquery` on the current workspace. 48 | analysis::ActionGraphContainer aquery(std::string const& query, 49 | std::vector const& bazel_flags, 50 | std::vector const& configs) const; 51 | 52 | private: 53 | bazel() = delete; 54 | bazel(std::filesystem::path bazel_command, 55 | std::vector bazel_startup_options, 56 | std::filesystem::path workspace, 57 | std::filesystem::path execution_root); 58 | 59 | private: 60 | std::filesystem::path bazel_command_; 61 | std::vector bazel_startup_options_; 62 | std::filesystem::path workspace_path_; 63 | std::filesystem::path execution_root_; 64 | }; 65 | } // namespace bcc 66 | -------------------------------------------------------------------------------- /bcc/compile_commands.cpp: -------------------------------------------------------------------------------- 1 | #include "bcc/compile_commands.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include "bcc/artifacts.hpp" 12 | #include "bcc/dep_set_of_files.hpp" 13 | #include "bcc/path_fragments.hpp" 14 | 15 | namespace bcc { 16 | 17 | namespace { 18 | 19 | /// starts_with for pre C++20 20 | bool 21 | starts_with(std::string_view const& sv, std::string_view const& prefix) 22 | { 23 | return std::string_view(sv.data(), std::min(sv.size(), prefix.size())) == prefix; 24 | } 25 | /// ends_with for pre C++20 26 | bool 27 | ends_with(std::string_view const& sv, std::string_view const& suffix) 28 | { 29 | return sv.size() >= suffix.size() && sv.compare(sv.size() - suffix.size(), std::string_view::npos, suffix) == 0; 30 | } 31 | 32 | bool 33 | is_cc_suffix(std::string_view const& v) 34 | { 35 | return ends_with(v, ".C") || ends_with(v, ".c") || ends_with(v, ".cc") || ends_with(v, ".cxx") || 36 | ends_with(v, ".c++") || ends_with(v, ".cpp") || ends_with(v, ".m") || ends_with(v, ".mm"); 37 | } 38 | 39 | /// Join an array of arguments into a commands string. 40 | std::string 41 | join_arguments(boost::json::array const& args) 42 | { 43 | auto cmd = std::stringstream(); 44 | bool need_sep = false; 45 | for (auto const& arg : args) { 46 | if (need_sep) { 47 | cmd << " "; 48 | } 49 | auto const has_space = arg.as_string().find(' ') == std::string_view::npos; 50 | auto const has_quote = arg.as_string().find('"') == std::string_view::npos; 51 | if (has_space || has_quote) { 52 | // no need to quote 53 | cmd << arg.as_string().c_str(); 54 | } else { 55 | // json::string will be quoted correctly when streamed 56 | cmd << arg.as_string(); 57 | } 58 | need_sep = true; 59 | } 60 | return cmd.str(); 61 | } 62 | } // namespace 63 | 64 | compile_commands_builder& 65 | compile_commands_builder::command(bool value) 66 | { 67 | command_ = value; 68 | return *this; 69 | } 70 | 71 | compile_commands_builder& 72 | compile_commands_builder::resolve(bool value) 73 | { 74 | resolve_ = value; 75 | return *this; 76 | } 77 | 78 | compile_commands_builder& 79 | compile_commands_builder::compiler(std::optional value) 80 | { 81 | compiler_ = std::move(value); 82 | return *this; 83 | } 84 | 85 | compile_commands_builder& 86 | compile_commands_builder::replacements(bcc::replacements value) 87 | { 88 | replacements_ = std::move(value); 89 | return *this; 90 | } 91 | 92 | compile_commands_builder& 93 | compile_commands_builder::workspace_path(std::filesystem::path value) 94 | { 95 | workspace_path_ = std::move(value); 96 | return *this; 97 | } 98 | 99 | compile_commands_builder& 100 | compile_commands_builder::execution_root(std::filesystem::path value) 101 | { 102 | execution_root_ = std::move(value); 103 | return *this; 104 | } 105 | 106 | boost::json::array 107 | compile_commands_builder::build(analysis::ActionGraphContainer const& action_graph) const 108 | { 109 | auto const fragements = path_fragments(action_graph.path_fragments()); 110 | auto const art = artifacts(action_graph.artifacts(), fragements); 111 | auto const dep_set = dep_set_of_files(action_graph.dep_set_of_files(), art); 112 | 113 | // the root element of a compile_commands.json document is an array of objects 114 | auto json = boost::json::array(); 115 | 116 | for (auto const& action : action_graph.actions()) { 117 | auto const action_args = action.arguments(); 118 | if (!action_args.empty()) { 119 | // arguments are optional in the action graph 120 | auto args = boost::json::array(); 121 | auto action_args_begin = std::begin(action_args); 122 | if (compiler_.has_value()) { 123 | args.push_back(boost::json::string(compiler_.value())); 124 | std::advance(action_args_begin, 1); ///< skip over the real compiler 125 | } 126 | std::transform(action_args_begin, std::end(action_args), std::back_inserter(args), [&](auto a) { 127 | return boost::json::string(replacements_.apply(a)); 128 | }); 129 | auto const output = art.path_of_artifact(action.primary_output_id()); 130 | auto file = std::optional{}; 131 | for (auto const& k : action.input_dep_set_ids()) { 132 | auto const set = dep_set.get(k); 133 | file = set.find_if(is_cc_suffix); 134 | if (file.has_value()) { 135 | break; 136 | } 137 | } 138 | 139 | // input file is required 140 | if (file.has_value()) { 141 | if (resolve_) { 142 | std::error_code ec; 143 | auto const resolved = std::filesystem::canonical(execution_root_ / file.value(), ec); 144 | if (!ec && starts_with(resolved.string(), workspace_path_.string())) { 145 | file = resolved.string(); 146 | } 147 | } 148 | auto obj = boost::json::object(); 149 | obj.insert(boost::json::object::value_type{ "directory", execution_root_.string() }); 150 | if (command_) { 151 | obj.insert(boost::json::object::value_type{ "command", join_arguments(args) }); 152 | } else { 153 | obj.insert(boost::json::object::value_type{ "arguments", args }); 154 | } 155 | obj.insert(boost::json::object::value_type{ "file", file.value() }); 156 | obj.insert(boost::json::object::value_type{ "output", output }); 157 | 158 | json.push_back(obj); 159 | } 160 | } 161 | } 162 | return json; 163 | } 164 | 165 | } // namespace bcc 166 | -------------------------------------------------------------------------------- /bcc/compile_commands.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "bcc/replacements.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include "src/main/protobuf/analysis_v2.pb.h" 12 | 13 | namespace bcc { 14 | 15 | class compile_commands_builder 16 | { 17 | public: 18 | /// Include `arguments` in final compile_commands.json file. 19 | compile_commands_builder& command(bool value); 20 | /// Resolve symlinks of file entries in the compile_commands.json file. 21 | compile_commands_builder& resolve(bool value); 22 | /// Set compiler. 23 | compile_commands_builder& compiler(std::optional value); 24 | /// Set workspace location. 25 | compile_commands_builder& workspace_path(std::filesystem::path value); 26 | /// Set execution_root. 27 | compile_commands_builder& execution_root(std::filesystem::path value); 28 | /// Set replacements. 29 | compile_commands_builder& replacements(bcc::replacements value); 30 | /// Turn actions from a bazel aquery into a compile_commands.json format. 31 | boost::json::array build(analysis::ActionGraphContainer const& action_graph) const; 32 | 33 | private: 34 | bool command_{ false }; 35 | bool resolve_{ false }; 36 | std::optional compiler_{}; 37 | bcc::replacements replacements_{}; 38 | std::filesystem::path workspace_path_{}; 39 | std::filesystem::path execution_root_{}; 40 | }; 41 | } // namespace bcc 42 | -------------------------------------------------------------------------------- /bcc/compile_commands_test.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | #include "bcc/compile_commands.hpp" 6 | 7 | using testing::IsTrue; 8 | 9 | namespace { 10 | 11 | TEST(compile_commands, simple) 12 | { 13 | EXPECT_THAT(true, IsTrue()); 14 | } 15 | 16 | } // namespace 17 | -------------------------------------------------------------------------------- /bcc/dep_set_of_files.cpp: -------------------------------------------------------------------------------- 1 | #include "bcc/dep_set_of_files.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace bcc { 9 | 10 | dep_set_of_files::dep_set_of_files(google::protobuf::RepeatedPtrField const& dep_set, 11 | artifacts a) 12 | : artifacts_(std::move(a)) 13 | { 14 | for (auto const& k : dep_set) { 15 | auto const e = 16 | entry{ std::vector(k.transitive_dep_set_ids().begin(), k.transitive_dep_set_ids().end()), 17 | std::vector(k.direct_artifact_ids().begin(), k.direct_artifact_ids().end()) }; 18 | table_.insert({ k.id(), e }); 19 | } 20 | } 21 | 22 | dep_set 23 | dep_set_of_files::get(std::uint32_t id) const 24 | { 25 | if (auto const it = cache_.find(id); it != cache_.end()) { 26 | return dep_set(it->second); 27 | } 28 | std::vector result; 29 | std::set visited_ids; 30 | 31 | std::queue q; 32 | 33 | auto const it = table_.find(id); 34 | 35 | for (auto const& k : it->second.direct) { 36 | result.push_back(artifacts_.path_of_artifact(k)); 37 | } 38 | for (auto const& k : it->second.transitive) { 39 | if (!visited_ids.insert(k).second) { 40 | continue; 41 | } 42 | auto const j = table_.find(k); 43 | assert(j != table_.end()); 44 | q.push(j->second); 45 | } 46 | 47 | while (!q.empty()) { 48 | auto const& front = q.front(); 49 | for (auto const& k : front.direct) { 50 | result.push_back(artifacts_.path_of_artifact(k)); 51 | } 52 | for (auto const& k : front.transitive) { 53 | if (!visited_ids.insert(k).second) { 54 | continue; 55 | } 56 | auto const j = table_.find(k); 57 | assert(j != table_.end()); 58 | q.push(j->second); 59 | } 60 | 61 | q.pop(); 62 | } 63 | 64 | auto const r = cache_.emplace(id, std::move(result)); 65 | assert(r.second == true); ///< insertion must happen when reaching here 66 | 67 | return dep_set(r.first->second); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /bcc/dep_set_of_files.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include "src/main/protobuf/analysis_v2.pb.h" 12 | 13 | #include "bcc/artifacts.hpp" 14 | 15 | namespace bcc { 16 | /// One dependency set 17 | class dep_set 18 | { 19 | friend class dep_set_of_files; 20 | 21 | public: 22 | template 23 | std::optional find_if(Predicate pred) const 24 | { 25 | auto result = std::optional{}; 26 | 27 | auto const it = std::find_if(std::begin(set_), std::end(set_), pred); 28 | if (it != std::end(set_)) { 29 | result = *it; 30 | } 31 | return result; 32 | } 33 | 34 | std::vector const& all() const { return set_; } 35 | 36 | private: 37 | explicit dep_set(std::vector set) 38 | : set_(std::move(set)) 39 | { 40 | } 41 | std::vector set_; 42 | }; 43 | 44 | /// Dependency set of files for lookup. 45 | class dep_set_of_files 46 | { 47 | struct entry 48 | { 49 | std::vector transitive; 50 | std::vector direct; 51 | }; 52 | 53 | public: 54 | explicit dep_set_of_files(google::protobuf::RepeatedPtrField const& dep_set, artifacts a); 55 | 56 | dep_set get(std::uint32_t id) const; 57 | 58 | private: 59 | std::unordered_map table_; 60 | 61 | mutable std::unordered_map> cache_; 62 | 63 | artifacts artifacts_; 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /bcc/main.cpp: -------------------------------------------------------------------------------- 1 | #include "bcc/bazel.hpp" 2 | #include "bcc/compile_commands.hpp" 3 | #include "bcc/options.hpp" 4 | #include "bcc/platform.hpp" 5 | #include "bcc/replacements.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | class file_error : public std::runtime_error 18 | { 19 | public: 20 | file_error(std::string_view what, std::filesystem::path const& path) 21 | : std::runtime_error([&]() { 22 | std::stringstream msg; 23 | msg << "failed to " << what << ": " << path.string(); 24 | return msg.str(); 25 | }()) 26 | { 27 | } 28 | }; 29 | 30 | std::string 31 | make_query(std::vector const& targets) 32 | { 33 | auto query = std::stringstream{}; 34 | query << "mnemonic('(Objc|Cpp)Compile',deps("; 35 | bool need_plus = false; 36 | for (auto const& target : targets) { 37 | if (need_plus) { 38 | query << " + "; 39 | } 40 | query << target; 41 | need_plus = true; 42 | } 43 | query << "))"; 44 | return query.str(); 45 | } 46 | 47 | std::string 48 | replace_workspace_placeholder(std::string value, std::string workspace_location) 49 | { 50 | bcc::replacements repl; 51 | 52 | repl.add({ "%workspace%", std::move(workspace_location) }); 53 | 54 | return repl.apply(std::move(value)); 55 | } 56 | 57 | int 58 | main(int argc, char** argv) 59 | { 60 | try { 61 | // When run via `bazel run ...` change directory to the current workspace 62 | if (getenv("BUILD_WORKSPACE_DIRECTORY")) { 63 | std::filesystem::current_path(getenv("BUILD_WORKSPACE_DIRECTORY")); 64 | } 65 | 66 | auto options = bcc::options::from_argv(argc, argv); 67 | 68 | if (options.bazel_command.empty() || !std::filesystem::exists(options.bazel_command)) { 69 | std::cerr << "fatal error: bazel or bazelisk command not found, please enure the command is in PATH or use " 70 | "`--bazel-command PATH'" 71 | << std::endl; 72 | return 1; 73 | } 74 | 75 | auto const bazel = bcc::bazel::create(options.bazel_command, options.bazel_startup_options); 76 | 77 | if (options.write_rc_file) { 78 | if (!options.rcpath.has_value()) { 79 | options.rcpath = bazel.workspace_path() / bcc::rc_name; 80 | } 81 | auto rcfile = std::ofstream(options.rcpath.value().c_str()); 82 | if (!rcfile) { 83 | throw file_error("open", options.rcpath.value()); 84 | } 85 | options.write(rcfile); 86 | if (!rcfile) { 87 | throw file_error("write", options.rcpath.value()); 88 | } 89 | } 90 | 91 | options.output_path = replace_workspace_placeholder(options.output_path, bazel.workspace_path().string()); 92 | if (options.verbose) { 93 | options.write(std::cerr); 94 | } 95 | 96 | auto replacements = bcc::platform_replacements(bazel.execution_root().string()); 97 | if (options.verbose) { 98 | for (auto const& def : replacements.definitions()) { 99 | std::cerr << def.first << "=" << def.second << std::endl; 100 | } 101 | } 102 | replacements.add_all(options.replace); 103 | 104 | auto builder = bcc::compile_commands_builder(); 105 | builder.command(options.command) 106 | .resolve(options.resolve) 107 | .replacements(replacements) 108 | .workspace_path(bazel.workspace_path()) 109 | .execution_root(bazel.execution_root()) 110 | .compiler(options.compiler); 111 | 112 | auto const query_str = make_query(options.targets); 113 | if (options.verbose) { 114 | std::cerr << "Query `" << query_str << '`' << std::endl; 115 | } 116 | auto const agc = bazel.aquery(query_str, options.bazel_flags, options.configs); 117 | auto const actions = agc.actions(); 118 | 119 | if (options.verbose) { 120 | std::cerr << "Build compile commands from " << agc.actions().size() << " actions" << std::endl; 121 | } 122 | 123 | auto const compile_commands = builder.build(agc); 124 | { 125 | if (options.verbose) { 126 | std::cerr << "Writing " << compile_commands.size() << " commands to `" << options.output_path << "`" 127 | << std::endl; 128 | } 129 | // Allow `-` as output_path to mean write to stdout instead of a file. 130 | auto compile_commands_file = std::ofstream{}; 131 | auto* compile_commands_buf = static_cast(nullptr); 132 | if (options.output_path == "-") { 133 | compile_commands_buf = std::cout.rdbuf(); 134 | } else { 135 | auto const output_path = std::filesystem::path(options.output_path); 136 | std::filesystem::create_directories(output_path.parent_path()); 137 | compile_commands_file.open(output_path.c_str()); 138 | if (!compile_commands_file) { 139 | throw file_error("open", output_path); 140 | } 141 | compile_commands_buf = compile_commands_file.rdbuf(); 142 | } 143 | assert(compile_commands_buf != nullptr); 144 | 145 | auto compile_commands_stream = std::ostream(compile_commands_buf); 146 | compile_commands_stream << compile_commands; 147 | if (!compile_commands_stream) { 148 | throw file_error("write", options.output_path); 149 | } 150 | } 151 | } catch (std::exception const& ex) { 152 | std::cerr << "fatal error: " << ex.what() << std::endl; 153 | return 1; 154 | } 155 | return 0; 156 | } 157 | -------------------------------------------------------------------------------- /bcc/options.cpp: -------------------------------------------------------------------------------- 1 | #include "bcc/options.hpp" 2 | #include "bcc/version.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | namespace po = boost::program_options; 13 | 14 | namespace bcc { 15 | namespace { 16 | 17 | std::optional 18 | find_bazelccrc() 19 | { 20 | auto dir = std::filesystem::current_path(); 21 | auto root = dir.root_path(); 22 | 23 | while (!dir.empty() || dir == root) { 24 | auto const rcpath = dir / rc_name; 25 | if (std::filesystem::exists(rcpath)) { 26 | 27 | return rcpath; 28 | } 29 | auto const parent_dir = dir.parent_path(); 30 | if (parent_dir == dir) { 31 | break; 32 | } 33 | dir = parent_dir; 34 | } 35 | return std::nullopt; 36 | } 37 | } 38 | 39 | char const* const rc_name = ".bazelccrc"; 40 | 41 | options 42 | options::from_argv(int argc, char* argv[]) 43 | { 44 | options result; 45 | 46 | result.rcpath = find_bazelccrc(); 47 | 48 | // Declare the supported options. 49 | po::options_description cfg; 50 | // clang-format off 51 | cfg.add_options() 52 | ("help,h", "produce help message") 53 | ("version,V", "print version") 54 | ("verbose,v", po::bool_switch(&result.verbose), "verbose, report more information") 55 | ("arguments,a", "provide `arguments` array in output (enabled by default)") 56 | ("command", po::bool_switch(&result.command), "provide `command` string in output") 57 | ("resolve", po::bool_switch(&result.resolve), "resolve file symlinks when their target is inside the workspace") 58 | ("bazel-command,B", po::value(&result.bazel_command)->value_name("PATH"), "bazel command") 59 | ("bazelsupopt,s", po::value(&result.bazel_startup_options)->value_name("OPTION"), "bazel startup options") 60 | ("bazelopt,b", po::value(&result.bazel_flags)->value_name("OPTION"), "bazel options") 61 | ("compiler,c", po::value()->value_name("PATH"), "use `compiler` as replacement for the bazel compiler wrapper script") 62 | ("config", po::value(&result.configs)->value_name("NAME"), "Bazel build config to apply") 63 | ("output,o", po::value(&result.output_path)->value_name("PATH"), "output path for the `compile_commands.json` file") 64 | ("replace,R", po::value>()->value_name("KEY=VALUE"), "Replace KEY with VALUE of each compile argument") 65 | ("targets", po::value>()->value_name("LABEL"), "Bazel target labels to query for compile commands") 66 | ; 67 | // clang-format on 68 | 69 | po::options_description desc("Usage: bazle-compile-commands [-hvcCBbsw] TARGETS"); 70 | desc.add(cfg); 71 | // clang-format off 72 | desc.add_options() 73 | ("write,w", po::bool_switch(&result.write_rc_file), "Write current supplied settings to a project specific config file") 74 | ; 75 | // clang-format on 76 | 77 | // All remaining arguments are considerd as bazel target labels. 78 | po::positional_options_description targets; 79 | targets.add("targets", -1); 80 | 81 | po::variables_map vm; 82 | // parse command line variables 83 | po::store(po::command_line_parser(argc, argv).options(desc).positional(targets).run(), vm); 84 | // parse rc file options when found 85 | if (result.rcpath.has_value()) { 86 | auto ifs = std::ifstream(result.rcpath.value().c_str()); 87 | if (ifs) { 88 | po::store(po::parse_config_file(ifs, cfg), vm); 89 | } 90 | } 91 | // done with command line parsing 92 | po::notify(vm); 93 | 94 | if (vm.count("help")) { 95 | std::cerr << desc << '\n'; 96 | std::exit(0); 97 | } 98 | 99 | if (vm.count("version")) { 100 | std::cerr << "bazel-compile-commands: " << BCC_VERSION << '\n'; 101 | std::exit(0); 102 | } 103 | 104 | if (vm.count("compiler")) { 105 | result.compiler = vm["compiler"].as(); 106 | } 107 | 108 | if (vm.count("replace")) { 109 | auto const rdef = vm["replace"].as>(); 110 | for (auto const& r : rdef) { 111 | auto const pos = r.find('='); 112 | if (pos == 0) { 113 | // ignore empty keys 114 | } else if (pos == std::string::npos) { 115 | // no '=' found 116 | result.replace.push_back({ r, "" }); 117 | } else { 118 | result.replace.push_back({ r.substr(0, pos), r.substr(pos + 1) }); 119 | } 120 | } 121 | } 122 | 123 | if (vm.count("targets")) { 124 | result.targets = vm["targets"].as>(); 125 | } 126 | 127 | if (result.bazel_command.empty()) { 128 | result.bazel_command = boost::process::search_path("bazelisk"); 129 | if (result.bazel_command.empty()) { 130 | result.bazel_command = boost::process::search_path("bazel"); 131 | } 132 | } else { 133 | if (!result.bazel_command.is_absolute()) { 134 | result.bazel_command = boost::process::search_path(result.bazel_command); 135 | } 136 | } 137 | if (!result.bazel_command.empty()) { 138 | result.bazel_command = std::filesystem::canonical(result.bazel_command); 139 | } 140 | return result; 141 | } 142 | 143 | std::ostream& 144 | options::write(std::ostream& os) const 145 | { 146 | os << "verbose = " << this->verbose << "\n"; 147 | os << "command = " << this->command << "\n"; 148 | os << "resolve = " << this->resolve << "\n"; 149 | os << "bazel-command = " << this->bazel_command << "\n"; 150 | for (auto const& opt : bazel_startup_options) { 151 | os << "bazelsupopt = " << opt << "\n"; 152 | } 153 | for (auto const& opt : bazel_flags) { 154 | os << "bazelopt = " << opt << "\n"; 155 | } 156 | if (compiler.has_value()) { 157 | os << "compiler = " << this->compiler.value() << "\n"; 158 | } 159 | os << "output = " << this->output_path << "\n"; 160 | for (auto const& r : this->replace) { 161 | os << "replace = " << r.first << "=" << r.second << "\n"; 162 | } 163 | for (auto const& c : this->configs) { 164 | os << "config = " << c << "\n"; 165 | } 166 | for (auto const& t : this->targets) { 167 | os << "targets = " << t << "\n"; 168 | } 169 | return os; 170 | } 171 | 172 | } // namespace bcc 173 | -------------------------------------------------------------------------------- /bcc/options.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace bcc { 9 | 10 | extern char const* const rc_name; 11 | 12 | struct options 13 | { 14 | static options from_argv(int argc, char* argv[]); 15 | 16 | /// Be verbose. 17 | bool verbose{ false }; 18 | /// Include `command` string in the final `compile_commands.json` 19 | bool command{ false }; 20 | /// Resolve symlinks of `file` entries in the `compile_commands.json` 21 | bool resolve{ false }; 22 | /// Bazel command. 23 | std::filesystem::path bazel_command{}; 24 | /// Replace Bazel compiler with `cc`. 25 | std::optional compiler{}; 26 | /// Bazel startup options. 27 | std::vector bazel_startup_options; 28 | /// Output path of the `compile_commands.json` file. 29 | std::string output_path{ "%workspace%/compile_commands.json" }; 30 | /// Bazel build configs to apply. 31 | std::vector configs{}; 32 | /// Any replacements to apply. 33 | std::vector> replace{}; 34 | /// Targets to consider for the generation of the `compile_commands.json` 35 | /// file. 36 | std::vector targets{ "//..." }; 37 | /// Flags to be forwarded to the `bazel query` call. 38 | std::vector bazel_flags{}; 39 | /// Write current config to file. 40 | bool write_rc_file{ false }; 41 | /// Path of config file if one is found. 42 | std::optional rcpath; 43 | 44 | std::ostream& write(std::ostream& os) const; 45 | }; 46 | } // namespace bcc 47 | -------------------------------------------------------------------------------- /bcc/path_fragments.cpp: -------------------------------------------------------------------------------- 1 | #include "bcc/path_fragments.hpp" 2 | 3 | #include 4 | 5 | namespace bcc { 6 | 7 | path_fragments::path_fragments(google::protobuf::RepeatedPtrField const& fragments) 8 | { 9 | for (auto const& k : fragments) { 10 | auto const e = entry{ k.label(), k.parent_id() }; 11 | fragments_.insert({ k.id(), e }); 12 | } 13 | } 14 | 15 | std::string 16 | path_fragments::build(std::uint32_t id) const 17 | { 18 | if (auto const it = cache_.find(id); it != cache_.end()) { 19 | return it->second.string(); 20 | } 21 | std::filesystem::path result; 22 | int path_fragment_id; 23 | 24 | // there must be at least on path fragment (i.e. no need to check for end) 25 | auto pfit = fragments_.find(id); 26 | assert(pfit != fragments_.end()); 27 | result = pfit->second.label; 28 | path_fragment_id = pfit->second.parent; 29 | 30 | pfit = fragments_.find(path_fragment_id); 31 | for (; path_fragment_id != 0 && pfit != fragments_.end(); pfit = fragments_.find(path_fragment_id)) { 32 | result = pfit->second.label / result; 33 | path_fragment_id = pfit->second.parent; 34 | } 35 | 36 | auto const r = cache_.insert({ id, result }); 37 | assert(r.second == true); ///< insertion must happen when reaching here 38 | 39 | return r.first->second.string(); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /bcc/path_fragments.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "src/main/protobuf/analysis_v2.pb.h" 8 | 9 | namespace bcc { 10 | /// Path fragment table to build full path from workspace. 11 | class path_fragments 12 | { 13 | public: 14 | struct entry 15 | { 16 | std::string label; ///< one fragment of the path 17 | std::uint32_t parent; ///< 0 is root 18 | }; 19 | 20 | explicit path_fragments(google::protobuf::RepeatedPtrField const& fragments); 21 | 22 | std::string build(std::uint32_t id) const; 23 | 24 | private: 25 | std::unordered_map fragments_; 26 | mutable std::unordered_map cache_; 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /bcc/platform.cpp: -------------------------------------------------------------------------------- 1 | #include "bcc/platform.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace bcc { 9 | 10 | namespace { 11 | /// Return the stdout content of a command. 12 | std::optional 13 | output_of(std::string_view cmd, std::vector args) 14 | { 15 | try { 16 | boost::process::ipstream outs; 17 | boost::process::ipstream errs; 18 | 19 | auto cmd_path = boost::process::search_path(cmd.data()); 20 | if (cmd_path.empty()) { 21 | return std::nullopt; 22 | } 23 | boost::process::child proc( 24 | cmd_path, boost::process::args(args), boost::process::std_out > outs, boost::process::std_err > errs); 25 | 26 | auto line = std::string{}; 27 | std::getline(outs, line); 28 | proc.wait(); 29 | auto const rc = proc.exit_code(); 30 | if (rc != 0) { 31 | std::ostringstream oss; 32 | oss << errs.rdbuf(); 33 | throw platform_error(oss.str()); 34 | } 35 | return line; 36 | } catch (boost::process::process_error const& ex) { 37 | return std::nullopt; 38 | } 39 | } 40 | } 41 | 42 | platform_error::platform_error(std::string const& what) 43 | : std::runtime_error(what) 44 | { 45 | } 46 | 47 | replacements 48 | platform_replacements(std::string execution_root) 49 | { 50 | replacements result; 51 | 52 | #if defined(__APPLE__) 53 | auto const devdir = output_of("xcode-select", { "--print-path" }); 54 | auto const sdkroot = output_of("xcrun", { "--show-sdk-path" }); 55 | 56 | // See 57 | // https://github.com/bazelbuild/bazel/blob/47edc57806056f3c8764241ed41b8acc72bd2ebf/tools/osx/crosstool/wrapped_clang.cc 58 | result.add({ "DEBUG_PREFIX_MAP_PWD=.", "-fdebug-prefix-map=" + execution_root + "=." }); 59 | if (devdir.has_value()) { 60 | result.add({ "__BAZEL_XCODE_DEVELOPER_DIR__", devdir.value() }); 61 | } 62 | if (devdir.has_value()) { 63 | result.add({ "__BAZEL_XCODE_SDKROOT__", sdkroot.value() }); 64 | } 65 | #else 66 | (void)execution_root; 67 | #endif 68 | 69 | return result; 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /bcc/platform.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "bcc/replacements.hpp" 4 | #include 5 | 6 | namespace bcc { 7 | /// Error occured during querying of the platform. 8 | class platform_error : public std::runtime_error 9 | { 10 | public: 11 | platform_error(std::string const& what); 12 | }; 13 | 14 | /// Returns platform specific replacements. 15 | replacements 16 | platform_replacements(std::string execution_root); 17 | } 18 | -------------------------------------------------------------------------------- /bcc/replacements.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "bcc/replacements.hpp" 4 | 5 | namespace bcc { 6 | void 7 | replacements::add(replacements::value_type value) 8 | { 9 | definitions_.emplace_back(std::move(value)); 10 | } 11 | 12 | void 13 | replacements::add_all(std::vector value) 14 | { 15 | std::move(value.begin(), value.end(), std::back_inserter(definitions_)); 16 | } 17 | 18 | std::string 19 | replacements::apply(std::string input) const 20 | { 21 | for (auto const& def : definitions_) { 22 | auto pos = std::size_t{ 0 }; 23 | auto fpos = std::size_t{ 0 }; 24 | while ((fpos = input.find(def.first, pos)) != std::string::npos) { 25 | input.replace(fpos, def.first.size(), def.second); 26 | // advance search position to find the next occourence 27 | pos += def.second.size(); 28 | } 29 | } 30 | return input; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /bcc/replacements.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace bcc { 8 | class replacements 9 | { 10 | public: 11 | /// `Every occurnce of `first` will be replaced with `second`. 12 | using value_type = std::pair; 13 | /// Add a replacement definition. 14 | void add(value_type value); 15 | /// Add all replacement definition. 16 | void add_all(std::vector value); 17 | /// Returns a string with all replacements applied. 18 | std::string apply(std::string input) const; 19 | /// Return list of replacements. 20 | std::vector const& definitions() const { return definitions_; } 21 | 22 | private: 23 | std::vector definitions_ = {}; 24 | }; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /bcc/replacements_test.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | #include "bcc/replacements.hpp" 6 | 7 | using testing::Eq; 8 | 9 | namespace { 10 | 11 | TEST(replacement, apply_empty) 12 | { 13 | 14 | auto repl = bcc::replacements(); 15 | auto r = repl.apply("TEST 123 abc"); 16 | 17 | EXPECT_THAT(r, Eq("TEST 123 abc")); 18 | } 19 | 20 | TEST(replacement, apply_simple) 21 | { 22 | 23 | auto repl = bcc::replacements(); 24 | repl.add({ "TEST", "not a test" }); 25 | auto r = repl.apply("TEST 123 abc"); 26 | 27 | EXPECT_THAT(r, Eq("not a test 123 abc")); 28 | } 29 | 30 | TEST(replacement, apply_complex) 31 | { 32 | 33 | auto repl = bcc::replacements(); 34 | repl.add({ "TEST", "xxx" }); 35 | repl.add({ "123", "one" }); 36 | auto r = repl.apply("TEST TESTTEST test TEST 123 abc"); 37 | 38 | EXPECT_THAT(r, Eq("xxx xxxxxx test xxx one abc")); 39 | } 40 | 41 | } // namespace 42 | -------------------------------------------------------------------------------- /copy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | # When no arguemnt is given, copy to the workspace root directory. 6 | dst=${1:-${BUILD_WORKSPACE_DIRECTORY}} 7 | 8 | # Take care of relative path, since this scripts working directoy is 9 | # not the one where `bazel run ...` was called. 10 | case "${dst}" in 11 | /*) ;; 12 | *) dst=${BUILD_WORKING_DIRECTORY}/${dst} ;; 13 | esac 14 | 15 | install -vm 644 "${PKG}" "${dst}" 16 | -------------------------------------------------------------------------------- /documentation.md: -------------------------------------------------------------------------------- 1 | # NAME 2 | 3 | `bazel-compile-commands` - generate a `compile_commands.json` file from a Bazel 4 | workspace 5 | 6 | # SYNOPSIS 7 | 8 | 9 | 10 | **bazel-compile-commands** [**-h**] 11 | 12 | **bazel-compile-commands** [**-av**] [**-b** BAZEL-OPTION] [**-B** BAZEL-COMMAND] [**-c** COMPILER] [**-o** OUTPUT-FILE] [**-s** BAZEL-STARTUP-OPTION] [**TARGETS**] 13 | 14 | 15 | 16 | # DESCRIPTION 17 | 18 | A non-intrusive way to generate a **compile_commands.json** file from a Bazel 19 | workspace. This is basically the **CMAKE_EXPORT_COMPILE_COMMANDS** option from 20 | CMake for Bazel. 21 | 22 | This works by issuing a bazel **aquery** and parsing this output and converting 23 | into the compile commands JSON format. 24 | 25 | When bazel-compile-commands is called it searches for a **.bazelccrc** file in 26 | the current directory or any parent directory. This file can be used to set 27 | default values for the arguments. The provided arguments via command line and 28 | from the configuration file are combined when bazel-compile-commands is called 29 | 30 | # OPTIONS 31 | 32 | -a, --arguments 33 | 34 | : Provided only for backwards compatibility. 35 | 36 | -B, --bazel-command **BAZEL** 37 | 38 | : Path to the **bazel** executable. Default value is **bazel**. Can be set to 39 | **bazelisk** to use bazelisk instead of bazel. 40 | 41 | -b, --bazelopt **OPTION** 42 | 43 | : **OPTION** will be forwarded to Bazel when querying Bazel for build actions. 44 | 45 | --config **NAME** 46 | 47 | : Same as `--bazelopt "--config=CONFIG"`. A config option from `.bazelrc` to 48 | apply when querying Bazel for build actions. 49 | 50 | --command 51 | 52 | : Generate a `compile_commands.json` file with `command` entries instead of 53 | `arguments` entries. 54 | 55 | -c, --compiler **FILE** 56 | 57 | : Replace the internal Bazel compiler wrapper script with this compiler (useful 58 | for macOS). 59 | 60 | -h, --help 61 | 62 | : Show the available options and exit. 63 | 64 | -o, --output **FILE** 65 | 66 | : Output file path of the `compile_commands.json` file. The default value is 67 | **%workspace%/compile_commands.json** the `%workspace%` part will be replaced 68 | with the actual worspace path which will be the output of 69 | `bazel info workspace`. 70 | 71 | -R, --replace KEY=VALUE 72 | 73 | : Replace occurrences of `KEY` with `VALUE` in the command and arguments field 74 | of each compile commands entry. Can be used to remove 75 | 76 | --resolve 77 | 78 | : Resolve symbolic links from the `execroot/` directory (used by Bazel as 79 | sandbox environment) to the absolute path. Only links of file which point into 80 | the **%workspace%** will be resolved. 81 | 82 | -s, --bazelsupopt **OPTION** 83 | 84 | : Additional Bazel startup options to be added to the `bazel` call. 85 | 86 | -v, --verbose 87 | 88 | : Be more verbose and print additional informations. 89 | 90 | -w, --write 91 | 92 | : Write the current supplied arguments into a **.bazelccrc** file such that 93 | further calls to bazel-compile-commands without arguments behaves the same as if 94 | the arguments are provided. 95 | 96 | If a .bazelccrc file aready exists this file will be updated. When no such file 97 | exists, a new file in the current Bazel workspace will be created. 98 | 99 | **TARGETS** 100 | 101 | : All remaining arguments are expected to be Bazel **targets** for which the 102 | compile commands shall be generated. The default is **//...** to generate the 103 | compile commands for all C/C++ files. 104 | 105 | # FILES 106 | 107 | bazel-compile-commands will search for a **.bazelccrc** file in the current 108 | directory or any parent directory. If such a file is found this values are used 109 | and combined with the values from the command line. 110 | 111 | An example **.bazelccrc** will look like: 112 | 113 | ``` 114 | verbose = 0 115 | arguments = 0 116 | bazel-command = bazel 117 | output = %workspace%/compile_commands.json 118 | targets = //... 119 | ``` 120 | 121 | # EXAMPLES 122 | 123 | ## REMOVE UNWANTED ARGUMENTS 124 | 125 | We can remove arguments from the resulting command and arguments entries of each 126 | compile commands entry like so: 127 | 128 | ```sh 129 | bazel-compile-commands --replace=-fno-canonical-system-headers= //... 130 | ``` 131 | 132 | With this, any occournce of `-fno-canonical-system-headers` will be removed (a 133 | flag which is understood by gcc but not by clang (including clangd and 134 | clang-tidy). 135 | 136 | ## Compile multiple different bazel-compile-commands outputs 137 | 138 | For uses cases like https://github.com/kiron1/bazel-compile-commands/issues/46 139 | where different Bazel targets require different build configurations one can use 140 | `jq` to combine multiple bazel-compile-commands outputs into one single 141 | `commpile_commands.json` file: 142 | 143 | ```bash 144 | jq -s '[ .[0] + .[1] | group_by(.output)[] | add ]' \ 145 | <(bazel-compile-commands -o - --config=configA //system-a//...) \ 146 | <(bazel-compile-commands -o - --config=configB //system-b//...) \ 147 | > compile_commands.json 148 | ``` 149 | 150 | We group by the output attribute, since this should be unique per 151 | `compile_commands.json` file (note: it is allowed when the same input `file` 152 | produces multiple district `output` files). 153 | 154 | More information about `jq` can be found at: https://jqlang.github.io/jq/manual/ 155 | -------------------------------------------------------------------------------- /tests/BUILD.bazel: -------------------------------------------------------------------------------- 1 | cc_test( 2 | name = "self_test", 3 | size = "small", 4 | srcs = ["self_test.cpp"], 5 | data = [ 6 | ":bazel-mock", 7 | "//bcc:bazel-compile-commands", 8 | ], 9 | defines = ["BOOST_PROCESS_USE_STD_FS=1"], 10 | deps = [ 11 | "@bazel_tools//tools/cpp/runfiles", 12 | "@boost.json", 13 | "@boost.process", 14 | "@com_google_googletest//:gtest_main", 15 | ], 16 | ) 17 | 18 | cc_binary( 19 | name = "bazel-mock", 20 | srcs = ["bazel_mock.cpp"], 21 | deps = ["//bcc:analysis"], 22 | ) 23 | -------------------------------------------------------------------------------- /tests/bazel_mock.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include "src/main/protobuf/analysis_v2.pb.h" 11 | 12 | namespace { 13 | char const* const aquery_textproto = R"( 14 | rule_classes { 15 | id: 1 16 | name: "cc_binary" 17 | } 18 | targets { 19 | id: 1 20 | label: "//:main" 21 | rule_class_id: 1 22 | } 23 | configuration { 24 | id: 1 25 | mnemonic: "darwin_arm64-fastbuild" 26 | platform_name: "darwin_arm64" 27 | checksum: "f4755fdba2a9adc4be1938f00075ca430c17dfc1e57b306638d8379237e33b52" 28 | } 29 | path_fragments { 30 | id: 3 31 | label: "external" 32 | } 33 | path_fragments { 34 | id: 2 35 | label: "local_config_cc" 36 | parent_id: 3 37 | } 38 | path_fragments { 39 | id: 1 40 | label: "cc_wrapper.sh" 41 | parent_id: 2 42 | } 43 | artifacts { 44 | id: 1 45 | path_fragment_id: 1 46 | } 47 | path_fragments { 48 | id: 4 49 | label: "libtool" 50 | parent_id: 2 51 | } 52 | artifacts { 53 | id: 2 54 | path_fragment_id: 4 55 | } 56 | path_fragments { 57 | id: 5 58 | label: "libtool_check_unique" 59 | parent_id: 2 60 | } 61 | artifacts { 62 | id: 3 63 | path_fragment_id: 5 64 | } 65 | path_fragments { 66 | id: 6 67 | label: "make_hashed_objlist.py" 68 | parent_id: 2 69 | } 70 | artifacts { 71 | id: 4 72 | path_fragment_id: 6 73 | } 74 | path_fragments { 75 | id: 7 76 | label: "wrapped_clang" 77 | parent_id: 2 78 | } 79 | artifacts { 80 | id: 5 81 | path_fragment_id: 7 82 | } 83 | path_fragments { 84 | id: 8 85 | label: "wrapped_clang_pp" 86 | parent_id: 2 87 | } 88 | artifacts { 89 | id: 6 90 | path_fragment_id: 8 91 | } 92 | path_fragments { 93 | id: 9 94 | label: "xcrunwrapper.sh" 95 | parent_id: 2 96 | } 97 | artifacts { 98 | id: 7 99 | path_fragment_id: 9 100 | } 101 | dep_set_of_files { 102 | id: 2 103 | direct_artifact_ids: 1 104 | direct_artifact_ids: 2 105 | direct_artifact_ids: 3 106 | direct_artifact_ids: 4 107 | direct_artifact_ids: 5 108 | direct_artifact_ids: 6 109 | direct_artifact_ids: 7 110 | } 111 | path_fragments { 112 | id: 10 113 | label: "main.cpp" 114 | } 115 | artifacts { 116 | id: 8 117 | path_fragment_id: 10 118 | } 119 | dep_set_of_files { 120 | id: 1 121 | transitive_dep_set_ids: 2 122 | direct_artifact_ids: 8 123 | } 124 | path_fragments { 125 | id: 16 126 | label: "bazel-out" 127 | } 128 | path_fragments { 129 | id: 15 130 | label: "darwin_arm64-fastbuild" 131 | parent_id: 16 132 | } 133 | path_fragments { 134 | id: 14 135 | label: "bin" 136 | parent_id: 15 137 | } 138 | path_fragments { 139 | id: 13 140 | label: "_objs" 141 | parent_id: 14 142 | } 143 | path_fragments { 144 | id: 12 145 | label: "main" 146 | parent_id: 13 147 | } 148 | path_fragments { 149 | id: 11 150 | label: "main.o" 151 | parent_id: 12 152 | } 153 | artifacts { 154 | id: 9 155 | path_fragment_id: 11 156 | } 157 | path_fragments { 158 | id: 17 159 | label: "main.d" 160 | parent_id: 12 161 | } 162 | artifacts { 163 | id: 10 164 | path_fragment_id: 17 165 | } 166 | actions { 167 | target_id: 1 168 | action_key: "7e536f8eda15e04d9292a64bb937d579272c49b8cf88bb77c4fc2f282a6b9ff3" 169 | mnemonic: "CppCompile" 170 | configuration_id: 1 171 | arguments: "cc" 172 | arguments: "-Wall" 173 | arguments: "-c" 174 | arguments: "main.cpp" 175 | arguments: "-o" 176 | arguments: "bazel-out/darwin_arm64-fastbuild/bin/_objs/main/main.o" 177 | environment_variables { 178 | key: "ZERO_AR_DATE" 179 | value: "1" 180 | } 181 | input_dep_set_ids: 1 182 | output_ids: 9 183 | output_ids: 10 184 | discovers_inputs: true 185 | execution_info { 186 | key: "requires-darwin" 187 | } 188 | execution_info { 189 | key: "supports-xcode-requirements-set" 190 | } 191 | primary_output_id: 9 192 | execution_platform: "@local_config_platform//:host" 193 | } 194 | )"; 195 | } 196 | 197 | int 198 | main(int argc, char** argv) 199 | { 200 | std::vector arguments(argv + 1, argv + argc); 201 | auto info_iter = std::find(std::begin(arguments), std::end(arguments), "info"); 202 | auto aquery_iter = std::find(std::begin(arguments), std::end(arguments), "aquery"); 203 | 204 | if (info_iter != std::end(arguments)) { 205 | ++info_iter; 206 | if (info_iter != std::end(arguments)) { 207 | if (*info_iter == std::string_view("workspace")) { 208 | std::cout << "/tmp/workspace\n"; 209 | return 0; 210 | } else if (*info_iter == std::string_view("execution_root")) { 211 | std::cout << "/tmp/execroot\n"; 212 | return 0; 213 | } else { 214 | std::cerr << "fatal error: invalid argument for workspace: invlaid location\n"; 215 | return 1; 216 | } 217 | } else { 218 | std::cerr << "fatal error: invalid argument for workspace: location required\n"; 219 | return 1; 220 | } 221 | } else if (aquery_iter != std::end(arguments)) { 222 | analysis::ActionGraphContainer agc; 223 | google::protobuf::TextFormat::ParseFromString(aquery_textproto, &agc); 224 | agc.SerializePartialToOstream(&std::cout); 225 | return 0; 226 | } else { 227 | std::cerr << "fatal error: invalid argument: unknown sub-command\n"; 228 | return 1; 229 | } 230 | 231 | return 0; 232 | } 233 | -------------------------------------------------------------------------------- /tests/self_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "tools/cpp/runfiles/runfiles.h" 12 | 13 | using bazel::tools::cpp::runfiles::Runfiles; 14 | using testing::Eq; 15 | using testing::Ge; 16 | using testing::IsEmpty; 17 | using testing::IsNull; 18 | using testing::IsTrue; 19 | using testing::Not; 20 | using testing::NotNull; 21 | 22 | namespace { 23 | std::string 24 | exe_suffix() 25 | { 26 | #ifdef _WIN32 27 | return ".exe"; 28 | #else 29 | return ""; 30 | #endif 31 | } 32 | 33 | boost::json::value 34 | run(std::filesystem::path const& commmand, std::vector args) 35 | { 36 | auto outs = boost::process::ipstream{}; 37 | auto proc = boost::process::child(commmand, boost::process::args(args), boost::process::std_out > outs); 38 | 39 | auto json_parser = boost::json::stream_parser{}; 40 | json_parser.reset(); 41 | auto line = std::string{}; 42 | 43 | while (std::getline(outs, line)) { 44 | auto ec = boost::system::error_code{}; 45 | json_parser.write(line, ec); 46 | if (ec) { 47 | throw std::runtime_error("invalid JSON"); 48 | } 49 | } 50 | proc.wait(); 51 | auto const rc = proc.exit_code(); 52 | if (rc != 0 || !json_parser.done()) { 53 | throw std::runtime_error("JSON error"); 54 | } 55 | 56 | return json_parser.release(); 57 | } 58 | 59 | TEST(self_test, run) 60 | { 61 | // TODO: Fix tests for Windows (I do not have Windows) 62 | #ifndef _WIN32 63 | std::string error; 64 | std::unique_ptr runfiles(Runfiles::CreateForTest(&error)); 65 | ASSERT_THAT(runfiles, NotNull()) << error; 66 | 67 | auto const bcc_path = runfiles->Rlocation("bazel-compile-commands/bcc/bazel-compile-commands") + exe_suffix(); 68 | ASSERT_THAT(bcc_path, Not(IsEmpty())) << bcc_path; 69 | ASSERT_THAT(std::filesystem::exists(bcc_path), IsTrue()) << bcc_path; 70 | 71 | auto const bazel_path = runfiles->Rlocation("bazel-compile-commands/tests/bazel-mock") + exe_suffix(); 72 | ASSERT_THAT(bazel_path, Not(IsEmpty())) << bazel_path; 73 | ASSERT_THAT(std::filesystem::exists(bazel_path), IsTrue()) << bazel_path; 74 | 75 | auto const result = run(bcc_path, { "-B", bazel_path.c_str(), "-o-" }); 76 | 77 | ASSERT_THAT(result.is_array(), IsTrue()); 78 | 79 | auto seen_files = std::map{}; 80 | seen_files.insert({ std::string("main.cpp"), false }); 81 | 82 | EXPECT_THAT(result.as_array().size(), Ge(seen_files.size())); 83 | 84 | for (auto const& cu : result.as_array()) { 85 | ASSERT_THAT(cu.is_object(), IsTrue()); 86 | auto const cu_obj = cu.as_object(); 87 | auto const end = cu_obj.end(); 88 | 89 | ASSERT_THAT(cu_obj.find("file"), Not(Eq(end))); 90 | EXPECT_THAT(cu_obj.find("directory"), Not(Eq(end))); 91 | EXPECT_THAT(cu_obj.find("command"), Eq(end)); 92 | EXPECT_THAT(cu_obj.find("arguments"), Not(Eq(end))); 93 | EXPECT_THAT(cu_obj.find("output"), Not(Eq(end))); 94 | 95 | auto const file = cu_obj.at("file").as_string(); 96 | auto const iter = seen_files.find(file.c_str()); 97 | if (iter != std::end(seen_files)) { 98 | iter->second = true; 99 | } 100 | } 101 | for (auto const& sf : seen_files) { 102 | EXPECT_THAT(sf.second, IsTrue()) << sf.first; 103 | } 104 | #endif 105 | } 106 | 107 | } // namespace 108 | -------------------------------------------------------------------------------- /tools/archive.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit -o nounset -o pipefail 4 | 5 | if [[ $# -lt 1 ]]; then 6 | echo "usage: ${0} TAG" 1>&2 7 | exit 1 8 | fi 9 | TAG=$1 10 | 11 | root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." &>/dev/null && pwd) 12 | 13 | if [[ -z "${GIT_DIR:-}" && -e "${root}/.jj/repo/store/git" ]]; then 14 | export GIT_DIR=${root}/.jj/repo/store/git 15 | fi 16 | 17 | # The prefix is chosen to match what GitHub generates for source archives 18 | PREFIX="bazel-compile-commands-${TAG}" 19 | ARCHIVE="bazel-compile-commands-${TAG}.tar.gz" 20 | git archive --output "${ARCHIVE}" --prefix "${PREFIX}/" "${TAG}" 21 | -------------------------------------------------------------------------------- /tools/clang-format: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Run clang format on all source files. 4 | # 5 | # Usage: 6 | # 7 | # ./tools/clang-format -i 8 | # 9 | # ./tools/clang-format --dry-run -Werror 10 | # 11 | 12 | find . -type d \( -name .git -o -name third_party \) -prune -false -o -type f -name '*.[ch]pp' \ 13 | -exec clang-format --style=file --fallback-style=none "$@" {} + 14 | -------------------------------------------------------------------------------- /tools/clang-tidy: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Run clang tidy on all source files. 4 | # 5 | # Usage: 6 | # 7 | # ./tools/clang-tidy 8 | # 9 | # ./tools/clang-tidy --fix 10 | # 11 | 12 | set -eu 13 | 14 | find . -type d \( -name .git -o -name third_party \) -prune -false -o -type f -name '*.cpp' \ 15 | -exec clang-tidy -p "$(pwd)" "$@" {} + 16 | -------------------------------------------------------------------------------- /tools/disk-cache-collect-garbage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # https://unix.stackexchange.com/a/461534 4 | # via https://github.com/bazelbuild/bazel/issues/5139#issuecomment-941525571 5 | # 6 | 7 | set -eu 8 | 9 | # find files; sort by last accessed time [%A@]; accumulate file size in 512B blocks [%b]; print path [%p] when capacity exceeded; delete 10 | find "${1}" -type f -printf '%A@ %b %p\0' | 11 | sort --numeric-sort --reverse --zero-terminated | 12 | awk --assign RS='\0' --assign ORS='\0' --assign CAPACITY=$((1 * 1024 ** 3 / 512)) '{du += $2}; du > CAPACITY { print $3 }' | 13 | xargs -r0 rm 14 | --------------------------------------------------------------------------------