Skip to content

Commit

Permalink
Add compile_commands.json generation
Browse files Browse the repository at this point in the history
The compile_commands.json is needed to run the language server.

Signed-off-by: Alexey Gladkov <[email protected]>
  • Loading branch information
legionus committed Nov 20, 2024
1 parent 2bda202 commit 80e8e6d
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@
!po/Makefile.in
.deps
.libs
.lsp.data
ABOUT-NLS
Makefile
Makefile.in
aclocal.m4
aminclude_static.am
autom4te.cache
compile_commands.json
config.cache
config.guess
config.h
Expand Down
9 changes: 9 additions & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ if BUILD_TESTS
SUBDIRS += tests
endif

.PHONY: compile_commands.json
compile_commands.json:
@make -C src $(MAKEFLAGS) REAL_CC="$(CC)" CC=$(CURDIR)/contrib/gen_compile_commands
@EXPORT_COMPILE_COMMANDS=1 $(CURDIR)/contrib/gen_compile_commands
@rm -f -- .lsp.data

clean-local:
rm -rf -- .lsp.data compile_commands.json

kbd-$(VERSION).tar.xz:
make distcheck

Expand Down
130 changes: 130 additions & 0 deletions contrib/gen_compile_commands
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#!/usr/bin/env python3

import json
import os
import subprocess
import sys

DATA_DIRNAME = ".lsp.data"
SUFFIXES = [".c", ".cpp", ".cxx"]


def get_topdir() -> str:
for name in ["TOPDIR", "KBUILD_ABS_SRCTREE"]:
if top_dir := os.getenv(name):
return os.path.realpath(top_dir)

dirname = os.getcwd()

while dirname != "/":
if os.path.isdir(os.path.join(dirname, ".git")):
return dirname
dirname = os.path.dirname(dirname)

return "/tmp"


def main():
args = sys.argv[1:]
cwd = os.getcwd()
real_compiler = os.getenv("REAL_CC")

command = [real_compiler, *args]
file = None

for arg in reversed(args):
try:
for suffix in SUFFIXES:
if arg.endswith(suffix):
file = os.path.realpath(arg, strict=True)
break
if file is not None:
break
except FileNotFoundError:
pass

if file is not None:
data_dir = os.path.join(get_topdir(), DATA_DIRNAME)

if not os.path.isdir(data_dir):
os.mkdir(data_dir, 0o700)

entry = {
"arguments": command,
"directory": cwd,
"file": os.path.realpath(file),
}

cmd_file = os.path.join(data_dir, file.replace("/", "_") + ".json")

with open(cmd_file, "w") as fh:
fh.write(json.dumps(entry, indent=4))

ret = subprocess.run(command)
sys.exit(ret.returncode)


def main_join():
top_dir = get_topdir()
out_file = os.path.join(top_dir, "compile_commands.json")

try:
outfh = open(out_file, "x+")
except FileExistsError:
outfh = open(out_file, "r+")

res = {}

if os.path.getsize(out_file) > 0:
try:
json_list = json.load(outfh)

if isinstance(json_list, list):
for ent in json_list:
res[ent["file"]] = ent

except json.decoder.JSONDecodeError:
pass

try:
data_dir = os.path.join(top_dir, DATA_DIRNAME)

for name in os.listdir(data_dir):
with open(os.path.join(data_dir, name), "r") as fh:
try:
data = json.load(fh)
res[data["file"]] = data

except json.decoder.JSONDecodeError:
pass

except FileNotFoundError:
outfh.close()
return

if res:
json_list = sorted(res.values(), key=lambda x: x["file"])

outfh.seek(0, os.SEEK_SET)
outfh.truncate(0)
outfh.write(json.dumps(json_list, indent=4))

outfh.close()
return


if __name__ == '__main__':
if sys.argv[0].endswith("_join") or os.getenv("EXPORT_COMPILE_COMMANDS"):
main_join()
elif len(sys.argv) > 1:
main()
else:
prog = os.path.basename(sys.argv[0])
print(
f"Usage: {prog} [COMPILER OPTIONS]... [INPUT FILES]...",
"",
"Compiler wrapper to save compiler options before compiling",
"and after create compile_commands.json.",
"",
sep="\n"
)

0 comments on commit 80e8e6d

Please sign in to comment.