使用eclipse开发C项目,比如stm32cubeide、s32ds时,由IDE生成makefile。如果你在VSCODE编辑代码,那是一片飘红,几乎没有智能提示。所以需要生成compile_commands.json ,喂给VSCODE。
使用方法(前提你已经生成了elf):
python
python gen_compile_commands_from_args.py 你的构建目录
比如
python gen_compile_commands_from_args.py App/Debug_FLASH #Debug_Flash是IDE已经生成好ELF的目录 这样 Debug_Flash目录下就有了compile_commands.json
python
#!/usr/bin/env python3
import argparse
import json
import re
from pathlib import Path
COMPILER_RE = re.compile(r"\b(arm-none-eabi-g\+\+|arm-none-eabi-gcc)\b")
ARGS_RE = re.compile(r'"@([^"]+\.args)"|@([^\s"]+\.args)')
RULE_RE = re.compile(r"^([^\s:]+)\s*:\s*(.+)$")
DRIVE_PATH_RE = re.compile(r"([A-Za-z]:[\\/][^\s\"']+)", re.IGNORECASE)
def read_text(path: Path) -> str:
return path.read_text(encoding="utf-8", errors="ignore")
def parse_source_lists(lines):
source_lists = {
"C_SRCS": [],
"CPP_SRCS": [],
"CXX_SRCS": [],
"CC_SRCS": [],
"C++_SRCS": [],
}
idx = 0
while idx < len(lines):
line = lines[idx].rstrip("\n")
m = re.match(r"^(C_SRCS|CPP_SRCS|CXX_SRCS|CC_SRCS|C\+\+_SRCS)\s*\+=\s*\\?\s*$", line)
if not m:
idx += 1
continue
key = m.group(1)
idx += 1
while idx < len(lines):
cur = lines[idx].rstrip("\n").strip()
if not cur:
break
if cur.endswith("\\"):
cur = cur[:-1].rstrip()
source_lists[key].append(cur)
idx += 1
idx += 1
return source_lists
def source_candidates_by_ext(source_lists):
out = {}
for key, values in source_lists.items():
for src in values:
ext = Path(src).suffix.lower()
out.setdefault(ext, []).append(src)
return out
def parse_args_file(args_path: Path):
def clean_token(token: str) -> str:
t = token.strip()
if not t:
return t
# key="value" -> key=value
if '="' in t and t.endswith('"'):
left, right = t.split('="', 1)
return f"{left}={right[:-1]}"
# -I"dir" / -include"file" -> -Idir / -includefile
for prefix in ("-I", "-isystem", "-iquote", "-include", "-imacros", "-MF", "-MT", "-MQ", "-o"):
if t.startswith(prefix + '"') and t.endswith('"'):
return prefix + t[len(prefix) + 1 : -1]
# "path" -> path
if len(t) >= 2 and t[0] == '"' and t[-1] == '"':
return t[1:-1]
return t
tokens = []
for raw in read_text(args_path).splitlines():
line = raw.strip()
if not line:
continue
tokens.append(clean_token(line))
return tokens
def normalize_source_for_db(src: str, build_dir: Path) -> str:
src = src.strip()
if re.match(r"^[A-Za-z]:/", src):
return src.replace("\\", "/")
abs_path = (build_dir / src).resolve()
return str(abs_path).replace("\\", "/")
def infer_lang_ext(prereq: str) -> str:
prereq = prereq.strip().strip('"')
if "%" in prereq:
low = prereq.lower()
if low.endswith("%.c"):
return ".c"
if low.endswith("%.cpp") or low.endswith("%.cc") or low.endswith("%.cxx"):
return ".cpp"
return ""
return Path(prereq).suffix.lower()
def pick_map_file(build_dir: Path):
expected_stem = build_dir.parent.name.lower()
exact = build_dir / f"{build_dir.parent.name}.map"
if exact.exists():
return exact
maps = list(build_dir.glob("*.map"))
for item in maps:
if item.stem.lower() == expected_stem:
return item
return maps[0] if maps else None
def resolve_toolchain_from_map(build_dir: Path):
map_file = pick_map_file(build_dir)
if not map_file:
return None, None, None
root = None
for raw_line in read_text(map_file).splitlines():
line = raw_line.strip()
if not line:
continue
lower = line.lower().replace("\\", "/")
marker = "bin/../lib/gcc/arm-none-eabi"
idx = lower.find(marker)
if idx < 0:
continue
prefix = line[:idx].replace("\\", "/").rstrip("/")
m = DRIVE_PATH_RE.search(prefix)
if not m:
continue
root = m.group(1).replace("\\", "/").rstrip("/")
break
if not root:
return map_file, None, None
gcc = f"{root}/bin/arm-none-eabi-gcc"
gpp = f"{root}/bin/arm-none-eabi-g++"
if Path(gcc + ".exe").exists():
gcc += ".exe"
if Path(gpp + ".exe").exists():
gpp += ".exe"
return map_file, gcc, gpp
def build_compiler_system_include_flags(gcc_path: str | None, gpp_path: str | None):
if not gcc_path:
return [], []
gcc_bin = Path(gcc_path)
toolchain_root = gcc_bin.parent.parent
gcc_lib_root = toolchain_root / "lib" / "gcc" / "arm-none-eabi"
c_flags = []
cxx_flags = []
def add_flag(flags, include_path: Path):
if include_path.exists():
flags.append(f"-isystem{str(include_path).replace('\\', '/')}")
# Common C/C++ headers.
add_flag(c_flags, toolchain_root / "arm-none-eabi" / "include")
add_flag(cxx_flags, toolchain_root / "arm-none-eabi" / "include")
if gcc_lib_root.exists():
version_dirs = [p for p in gcc_lib_root.iterdir() if p.is_dir()]
version_dirs.sort()
for version_dir in version_dirs:
add_flag(c_flags, version_dir / "include")
add_flag(c_flags, version_dir / "include-fixed")
add_flag(cxx_flags, version_dir / "include")
add_flag(cxx_flags, version_dir / "include-fixed")
# C++ standard library headers.
cxx_root = toolchain_root / "arm-none-eabi" / "include" / "c++" / version_dir.name
add_flag(cxx_flags, cxx_root)
add_flag(cxx_flags, cxx_root / "arm-none-eabi")
add_flag(cxx_flags, cxx_root / "backward")
# Remove duplicates while preserving order.
c_flags = list(dict.fromkeys(c_flags))
cxx_flags = list(dict.fromkeys(cxx_flags))
return c_flags, cxx_flags
def append_unique_flags(arg_tokens, extra_flags):
if not extra_flags:
return arg_tokens
token_set = set(arg_tokens)
merged = list(arg_tokens)
for flag in extra_flags:
if flag not in token_set:
merged.append(flag)
token_set.add(flag)
return merged
def collect_entries(
build_dir: Path,
gcc_path: str | None = None,
gpp_path: str | None = None,
c_sys_flags=None,
cxx_sys_flags=None,
):
entries = []
seen = set()
for subdir_mk in build_dir.rglob("subdir.mk"):
lines = read_text(subdir_mk).splitlines(keepends=True)
source_lists = parse_source_lists(lines)
by_ext = source_candidates_by_ext(source_lists)
i = 0
while i < len(lines):
line = lines[i].rstrip("\n")
rule_match = RULE_RE.match(line)
if not rule_match:
i += 1
continue
target = rule_match.group(1).strip()
prereq_all = rule_match.group(2).strip()
prereq_first = prereq_all.split()[0]
j = i + 1
compiler = None
args_rel = None
while j < len(lines) and lines[j].startswith("\t"):
cmd = lines[j].strip()
comp = COMPILER_RE.search(cmd)
args_m = ARGS_RE.search(cmd)
if comp and args_m:
compiler = comp.group(1)
if compiler == "arm-none-eabi-gcc" and gcc_path:
compiler = gcc_path
elif compiler == "arm-none-eabi-g++" and gpp_path:
compiler = gpp_path
args_rel = (args_m.group(1) or args_m.group(2)).replace("\\", "/")
break
j += 1
if not compiler or not args_rel:
i = j
continue
args_path = build_dir / args_rel
if not args_path.exists():
i = j
continue
arg_tokens = parse_args_file(args_path)
if compiler.endswith("arm-none-eabi-g++") or compiler.endswith("arm-none-eabi-g++.exe"):
arg_tokens = append_unique_flags(arg_tokens, cxx_sys_flags or [])
else:
arg_tokens = append_unique_flags(arg_tokens, c_sys_flags or [])
if "%" in target or "%" in prereq_first:
ext = infer_lang_ext(prereq_first)
for src in by_ext.get(ext, []):
file_path = normalize_source_for_db(src, build_dir)
if file_path in seen:
continue
seen.add(file_path)
entries.append(
{
"directory": str(build_dir).replace("\\", "/"),
"file": file_path,
"arguments": [compiler, *arg_tokens, file_path],
}
)
else:
file_path = normalize_source_for_db(prereq_first, build_dir)
if file_path not in seen:
seen.add(file_path)
entries.append(
{
"directory": str(build_dir).replace("\\", "/"),
"file": file_path,
"arguments": [compiler, *arg_tokens, file_path],
}
)
i = j
entries.sort(key=lambda x: x["file"])
return entries
def find_build_dirs(project_dir: Path):
candidates = []
for child in project_dir.iterdir():
if not child.is_dir():
continue
if (child / "makefile").exists() and (child / "sources.mk").exists():
subdir_count = len(list(child.rglob("subdir.mk")))
candidates.append((child, subdir_count))
candidates.sort(key=lambda item: item[1], reverse=True)
return candidates
def main():
parser = argparse.ArgumentParser(
description="Generate compile_commands.json from S32DS subdir.mk and .args files."
)
parser.add_argument(
"--build-dir",
default="auto",
help="S32DS build directory path, or 'auto' (default: auto)",
)
parser.add_argument(
"--list-build-dirs",
action="store_true",
help="List detected S32DS build directories and exit",
)
parser.add_argument(
"--output",
default="compile_commands.json",
help="Output file name or absolute path (default: compile_commands.json under build dir)",
)
args = parser.parse_args()
script_dir = Path(__file__).resolve().parent
detected = find_build_dirs(script_dir)
if args.list_build_dirs:
if not detected:
print("No build directories detected.")
return
for item, count in detected:
print(f"{item.name} (subdir.mk: {count})")
return
if args.build_dir.lower() == "auto":
if not detected:
raise SystemExit("No build directory detected. Use --build-dir <path>.")
build_dir = detected[0][0]
if len(detected) > 1:
others = ", ".join(d[0].name for d in detected[1:4])
print(f"Auto-selected build dir: {build_dir.name}")
print(f"Other detected dirs: {others}")
else:
print(f"Auto-selected build dir: {build_dir.name}")
else:
candidate = Path(args.build_dir)
build_dir = candidate if candidate.is_absolute() else (script_dir / candidate)
build_dir = build_dir.resolve()
if not build_dir.exists():
raise SystemExit(f"Build directory not found: {build_dir}")
output_arg = Path(args.output)
if output_arg.is_absolute():
output_path = output_arg
else:
output_path = (build_dir / output_arg).resolve()
output_path.parent.mkdir(parents=True, exist_ok=True)
map_file, gcc_path, gpp_path = resolve_toolchain_from_map(build_dir)
if map_file:
print(f"Map file: {map_file}")
if gcc_path and gpp_path:
print(f"Toolchain from map: {gcc_path}")
else:
print("Toolchain path not found in map, keeping arm-none-eabi-gcc/g++")
else:
print("Map file not found, keeping arm-none-eabi-gcc/g++")
c_sys_flags, cxx_sys_flags = build_compiler_system_include_flags(gcc_path, gpp_path)
if c_sys_flags or cxx_sys_flags:
print(
f"Added system include flags: C={len(c_sys_flags)} C++={len(cxx_sys_flags)}"
)
entries = collect_entries(
build_dir,
gcc_path=gcc_path,
gpp_path=gpp_path,
c_sys_flags=c_sys_flags,
cxx_sys_flags=cxx_sys_flags,
)
output_path.write_text(json.dumps(entries, indent=2), encoding="utf-8")
print(f"Generated {len(entries)} entries: {output_path}")
if __name__ == "__main__":
main()