import json from pathlib import Path import re import subprocess def load_idedata(environment, temp_folder, platformio_ini): build_environment = environment.replace("-tidy", "") build_dir = Path(temp_folder) / f"build-{build_environment}" Path(build_dir).mkdir(exist_ok=True) Path(build_dir / "platformio.ini").write_text( Path(platformio_ini).read_text(encoding="utf-8"), encoding="utf-8" ) esphome_dir = Path(build_dir / "esphome") esphome_dir.mkdir(exist_ok=True) Path(esphome_dir / "main.cpp").write_text( """ #include int main() { return 0;} """, encoding="utf-8", ) zephyr_dir = Path(build_dir / "zephyr") zephyr_dir.mkdir(exist_ok=True) Path(zephyr_dir / "prj.conf").write_text( """ CONFIG_NEWLIB_LIBC=y """, encoding="utf-8", ) subprocess.run(["pio", "run", "-e", build_environment, "-d", build_dir], check=True) def extract_include_paths(command): include_paths = [] include_pattern = re.compile(r'("-I\s*[^"]+)|(-isystem\s*[^\s]+)|(-I\s*[^\s]+)') for match in include_pattern.findall(command): split_strings = re.split( r"\s*-\s*(?:I|isystem)", list(filter(lambda x: x, match))[0] ) include_paths.append(split_strings[1]) return include_paths def extract_defines(command): defines = [] define_pattern = re.compile(r"-D\s*([^\s]+)") for match in define_pattern.findall(command): if match not in ("_ASMLANGUAGE"): defines.append(match) return defines def find_cxx_path(commands): for entry in commands: command = entry["command"] cxx_path = command.split()[0] if not cxx_path.endswith("++"): continue return cxx_path def get_builtin_include_paths(compiler): result = subprocess.run( [compiler, "-E", "-x", "c++", "-", "-v"], input="", text=True, stderr=subprocess.PIPE, stdout=subprocess.DEVNULL, check=True, ) include_paths = [] start_collecting = False for line in result.stderr.splitlines(): if start_collecting: if line.startswith(" "): include_paths.append(line.strip()) else: break if "#include <...> search starts here:" in line: start_collecting = True return include_paths def extract_cxx_flags(command): flags = [] # Extracts CXXFLAGS from the command string, excluding includes and defines. flag_pattern = re.compile( r"(-O[0-3s]|-g|-std=[^\s]+|-Wall|-Wextra|-Werror|--[^\s]+|-f[^\s]+|-m[^\s]+|-imacros\s*[^\s]+)" ) for match in flag_pattern.findall(command): flags.append(match.replace("-imacros ", "-imacros")) return flags def transform_to_idedata_format(compile_commands): cxx_path = find_cxx_path(compile_commands) idedata = { "includes": { "toolchain": get_builtin_include_paths(cxx_path), "build": set(), }, "defines": set(), "cxx_path": cxx_path, "cxx_flags": set(), } for entry in compile_commands: command = entry["command"] exec = command.split()[0] if exec != cxx_path: continue idedata["includes"]["build"].update(extract_include_paths(command)) idedata["defines"].update(extract_defines(command)) idedata["cxx_flags"].update(extract_cxx_flags(command)) # Convert sets to lists for JSON serialization idedata["includes"]["build"] = list(idedata["includes"]["build"]) idedata["defines"] = list(idedata["defines"]) idedata["cxx_flags"] = list(idedata["cxx_flags"]) return idedata compile_commands = json.loads( Path( build_dir / ".pio" / "build" / build_environment / "compile_commands.json" ).read_text(encoding="utf-8") ) return transform_to_idedata_format(compile_commands)