Add clangd support for VS Code (#2207)

* Add clangd support for VS Code

* Revert accidental change
This commit is contained in:
Luke Street 2024-10-12 22:43:10 -06:00 committed by GitHub
parent 7ec2d80414
commit 0db466edfa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 436 additions and 119 deletions

38
.gitignore vendored
View File

@ -1,13 +1,35 @@
# IDE folders
.idea/
.vs/
# Caches
__pycache__
.idea
.vscode
.ninja_*
.mypy_cache
*.exe
build
build.ninja
objdiff.json
.cache/
# Original files
orig/*/*
!orig/*/.gitkeep
*.dol
*.rel
*.elf
*.o
*.map
*.MAP
# Build files
build/
.ninja_*
build.ninja
# decompctx output
ctx.*
*.ctx
# Generated configs
objdiff.json
compile_commands.json
# Miscellaneous
/*.txt
ctx.c
*.exe

12
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,12 @@
{
"recommendations": [
"llvm-vs-code-extensions.vscode-clangd",
"ms-python.black-formatter",
"ms-python.flake8",
],
"unwantedRecommendations": [
"ms-vscode.cmake-tools",
"ms-vscode.cpptools-extension-pack",
"ms-vscode.cpptools",
]
}

23
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,23 @@
{
"[c]": {
"files.encoding": "utf8",
"editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd"
},
"[cpp]": {
"files.encoding": "utf8",
"editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd"
},
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter"
},
"editor.tabSize": 4,
"files.autoSave": "onFocusChange",
"files.insertFinalNewline": true,
"files.trimFinalNewlines": true,
"files.associations": {
"*.inc": "c",
".clangd": "yaml"
},
// Disable C/C++ IntelliSense, use clangd instead
"C_Cpp.intelliSenseEngine": "disabled",
}

16
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,16 @@
{
// Use Ctrl+Shift+B to run build tasks.
// Or "Run Build Task" in the Command Palette.
"version": "2.0.0",
"tasks": [
{
"label": "ninja",
"type": "shell",
"command": "ninja",
"group": {
"kind": "build",
"isDefault": true
}
},
]
}

View File

@ -249,9 +249,9 @@ struct TLinkList_factory : public TLinkList<T, I> {
virtual T* Do_create() = 0;
virtual void Do_destroy(T*) = 0;
void Clear_destroy() {
while (!empty()) {
T* item = &front();
pop_front();
while (!this->empty()) {
T* item = &this->front();
this->pop_front();
Do_destroy(item);
}
}
@ -298,16 +298,16 @@ struct TEnumerator2 {
};
template <typename T, int I>
struct TContainerEnumerator : public TEnumerator2<TLinkList<T, I>::iterator, T> {
struct TContainerEnumerator : public TEnumerator2<typename TLinkList<T, I>::iterator, T> {
inline TContainerEnumerator(TLinkList<T, I>* param_0)
: TEnumerator2<TLinkList<T, I>::iterator, T>(param_0->begin(), param_0->end()) {}
: TEnumerator2<typename TLinkList<T, I>::iterator, T>(param_0->begin(), param_0->end()) {}
};
template <typename T, int I>
struct TContainerEnumerator_const : public TEnumerator2<TLinkList<T, I>::const_iterator, const T> {
struct TContainerEnumerator_const : public TEnumerator2<typename TLinkList<T, I>::const_iterator, const T> {
inline TContainerEnumerator_const(const TLinkList<T, I>* param_0)
: TEnumerator2<TLinkList<T, I>::const_iterator, const T>(param_0->begin(), param_0->end()) {}
: TEnumerator2<typename TLinkList<T, I>::const_iterator, const T>(param_0->begin(), param_0->end()) {}
};
}; // namespace JGadget

View File

@ -2,7 +2,7 @@
#define DOL2ASM
// this helps remove useless error from the linter when using vscode.
#ifndef IN_VSCODE_EDITOR
#ifdef __MWERKS__
#ifdef __cplusplus
#define SECTION_INIT extern "C" __declspec(section ".init")

View File

@ -8,8 +8,8 @@
extern "C" {
#endif
volatile u16 __DSPRegs[32] : 0xCC005000;
volatile u32 __AIRegs[8] : 0xCC006C00;
volatile u16 __DSPRegs[32] AT_ADDRESS(0xCC005000);
volatile u32 __AIRegs[8] AT_ADDRESS(0xCC006C00);
#define DSP_TASK_FLAG_CLEARALL 0x00000000
#define DSP_TASK_FLAG_ATTACHED 0x00000001

View File

@ -4,6 +4,6 @@
#include "dolphin/types.h"
typedef void (*DVDLowCallback)(u32 intType);
vu32 __DIRegs[16] : 0xCC006000;
vu32 __DIRegs[16] AT_ADDRESS(0xCC006000);
#endif /* DVDLOW_H */

View File

@ -9,7 +9,7 @@ extern "C" {
typedef struct OSContext OSContext;
vu32 __EXIRegs[16] : 0xCC006800;
vu32 __EXIRegs[16] AT_ADDRESS(0xCC006800);
#define EXI_MEMORY_CARD_59 0x00000004
#define EXI_MEMORY_CARD_123 0x00000008

View File

@ -58,7 +58,7 @@ typedef union {
} PPCWGPipe;
#define GXFIFO_ADDR 0xCC008000
volatile PPCWGPipe GXWGFifo : GXFIFO_ADDR;
volatile PPCWGPipe GXWGFifo AT_ADDRESS(GXFIFO_ADDR);
#define GX_WRITE_U8(data) GXWGFifo.u8 = data;
#define GX_WRITE_U32(data) GXWGFifo.u32 = data;

View File

@ -65,19 +65,19 @@ extern "C" {
#define OS_CONSOLE_PC_EMULATOR 0x10000001
#define OS_CONSOLE_EMULATOR 0x10000000
volatile u16 __OSDeviceCode : 0x800030E6;
volatile u16 __OSDeviceCode AT_ADDRESS(0x800030E6);
volatile u32 OS_PI_INTR_CAUSE : 0xCC003000;
volatile u32 OS_PI_INTR_MASK : 0xCC003004;
volatile u32 OS_PI_INTR_CAUSE AT_ADDRESS(0xCC003000);
volatile u32 OS_PI_INTR_MASK AT_ADDRESS(0xCC003004);
volatile u16 OS_MI_INTR_MASK : 0xCC00401C;
volatile u16 OS_MI_INTR_MASK AT_ADDRESS(0xCC00401C);
volatile u16 OS_DSP_DMA_ADDR_HI : 0xCC005030;
volatile u16 OS_DSP_DMA_ADDR_LO : 0xCC005032;
volatile u16 OS_DSP_INTR_MASK : 0xCC00500A;
volatile u16 OS_DSP_DMA_ADDR_HI AT_ADDRESS(0xCC005030);
volatile u16 OS_DSP_DMA_ADDR_LO AT_ADDRESS(0xCC005032);
volatile u16 OS_DSP_INTR_MASK AT_ADDRESS(0xCC00500A);
volatile u16 OS_ARAM_DMA_ADDR_HI : 0xCC005020;
volatile u16 OS_ARAM_DMA_ADDR_LO : 0xCC005022;
volatile u16 OS_ARAM_DMA_ADDR_HI AT_ADDRESS(0xCC005020);
volatile u16 OS_ARAM_DMA_ADDR_LO AT_ADDRESS(0xCC005022);
BOOL OSIsThreadSuspended(OSThread* thread);

View File

@ -151,8 +151,8 @@ typedef struct OSContext {
/* 0x1C4 */ f64 ps[32];
} OSContext;
OSContext* OS_CURRENT_CONTEXT : 0x800000D4;
OSContext* OS_CURRENT_FPU_CONTEXT : 0x800000D8;
OSContext* OS_CURRENT_CONTEXT AT_ADDRESS(0x800000D4);
OSContext* OS_CURRENT_FPU_CONTEXT AT_ADDRESS(0x800000D8);
void __OSLoadFPUContext(void);
void __OSSaveFPUContext(s32 unused0, s32 unused1, OSContext* context);

View File

@ -1,6 +1,7 @@
#ifndef OSLINK_H
#define OSLINK_H
#include "dolphin/types.h"
#ifdef __cplusplus
extern "C" {
@ -18,14 +19,14 @@ typedef struct OSSectionInfo OSSectionInfo;
typedef struct OSImportInfo OSImportInfo;
typedef struct OSRel OSRel;
OSModuleQueue __OSModuleList : 0x800030C8;
void* __OSStringTable : 0x800030D0;
struct OSModuleQueue {
OSModuleInfo* head;
OSModuleInfo* tail;
};
OSModuleQueue __OSModuleList AT_ADDRESS(0x800030C8);
void* __OSStringTable AT_ADDRESS(0x800030D0);
struct OSModuleLink {
OSModuleInfo* next;
OSModuleInfo* prev;

View File

@ -7,7 +7,7 @@
extern "C" {
#endif
vu32 __PIRegs[12] : 0xCC003000;
vu32 __PIRegs[12] AT_ADDRESS(0xCC003000);
#define OS_RESETCODE_RESTART 0x80000000
#define OS_RESETCODE_SYSTEM 0x40000000

View File

@ -75,8 +75,8 @@ struct OSThread {
typedef void (*OSSwitchThreadCallback)(OSThread* from, OSThread* to);
OSThreadQueue OS_THREAD_QUEUE : 0x800000DC;
OSThread* OS_CURRENT_THREAD : 0x800000E4;
OSThreadQueue OS_THREAD_QUEUE AT_ADDRESS(0x800000DC);
OSThread* OS_CURRENT_THREAD AT_ADDRESS(0x800000E4);
static void DefaultSwitchThreadCallback(OSThread* from, OSThread* to);
OSSwitchThreadCallback OSSetSwitchThreadCallback(OSSwitchThreadCallback func);

View File

@ -10,7 +10,7 @@ extern "C" {
typedef s64 OSTime;
typedef u32 OSTick;
OSTime OS_SYSTEM_TIME : 0x800030D8;
OSTime OS_SYSTEM_TIME AT_ADDRESS(0x800030D8);
typedef struct OSCalendarTime {
/* 0x00 */ s32 seconds;
@ -32,7 +32,7 @@ OSTime __OSTimeToSystemTime(OSTime time);
void GetDates(s32 days, OSCalendarTime* ct);
void OSTicksToCalendarTime(OSTime ticks, OSCalendarTime* ct);
extern u32 __OSBusClock : 0x800000F8;
extern u32 __OSBusClock AT_ADDRESS(0x800000F8);
#define OS_BUS_CLOCK (__OSBusClock)
#define OS_CORE_CLOCK (*(u32*)0x800000FC)

View File

@ -120,7 +120,7 @@ BOOL SITransfer(s32 chan, void* output, u32 outputBytes, void* input, u32 inputB
u32 SIGetType(s32 chan);
u32 SIGetTypeAsync(s32 chan, SITypeAndStatusCallback callback);
vu32 __SIRegs[64] : 0xCC006400;
vu32 __SIRegs[64] AT_ADDRESS(0xCC006400);
#ifdef __cplusplus
}

View File

@ -42,4 +42,10 @@ typedef int BOOL;
#define FLOAT_MIN (1.175494351e-38f)
#define FLOAT_MAX (3.40282346638528860e+38f)
#ifdef __MWERKS__
#define AT_ADDRESS(xyz) : (xyz)
#else
#define AT_ADDRESS(xyz)
#endif
#endif

View File

@ -138,7 +138,7 @@ u32 VIGetRetraceCount();
u32 VIGetDTVStatus();
u32 VIGetTvFormat(void);
vu16 __VIRegs[59] : 0xCC002000;
vu16 __VIRegs[59] AT_ADDRESS(0xCC002000);
#ifdef __cplusplus
};

View File

@ -24,7 +24,7 @@
#define _SDA_BASE_(dummy) 0
#define _SDA2_BASE_(dummy) 0
#ifndef IN_VSCODE_EDITOR
#ifdef __MWERKS__
#define GLUE(a, b) a##b
#define GLUE2(a, b) GLUE(a, b)
#define STATIC_ASSERT(cond) typedef char GLUE2(static_assertion_failed, __LINE__)[(cond) ? 1 : -1]

View File

@ -19,7 +19,7 @@ extern "C" void* __va_arg(_va_list_struct*, int);
void* __va_arg(_va_list_struct*, int);
#endif
#if IN_VSCODE_EDITOR
#ifndef __MWERKS__
#define __builtin_va_info(...)
#define _var_arg_typeof(...)
#endif

View File

@ -3,7 +3,7 @@
#include "dolphin/card/CARDPriv.h"
#include "dolphin/os/OSRtc.h"
u8 GameChoice : 0x800030E3;
u8 GameChoice AT_ADDRESS(0x800030E3);
static BOOL IsCard(u32 id);
static s32 DoMount(s32 chan);

View File

@ -678,7 +678,7 @@ asm void __OSPSInit(void){
// clang-format on
}
vu32 __DIRegs[16] : 0xCC006000;
vu32 __DIRegs[16] AT_ADDRESS(0xCC006000);
#define DI_CONFIG_IDX 0x9
#define DI_CONFIG_CONFIG_MASK 0xFF

View File

@ -104,7 +104,7 @@ static void Callback(s32, DVDCommandBlock*) {
Prepared = TRUE;
}
OSExecParams* osExecParams : 0x800030f0;
OSExecParams* osExecParams AT_ADDRESS(0x800030f0);
static int IsStreamEnabled() {
if (DVDGetCurrentDiskID()->is_streaming) {
@ -148,7 +148,7 @@ static void StopStreaming() {
}
/* 8033CCFC-8033CDC0 33763C 00C4+00 1/1 0/0 0/0 .text GetApploaderPosition */
s32 __OSAppLoaderOffset : 0x800030f4;
s32 __OSAppLoaderOffset AT_ADDRESS(0x800030f4);
static int GetApploaderPosition(void) {
static s32 apploaderPosition;
@ -220,9 +220,9 @@ static BOOL IsNewApploader(AppLoaderStruct* header) {
return strncmp(header->date, "2004/02/01", 10) > 0 ? TRUE : FALSE;
}
extern volatile u32 BOOT_REGION_START : 0x812FDFF0;
extern volatile u32 BOOT_REGION_END : 0x812FDFEC;
extern volatile u8 g_unk_800030E2 : 0x800030E2;
extern volatile u32 BOOT_REGION_START AT_ADDRESS(0x812FDFF0);
extern volatile u32 BOOT_REGION_END AT_ADDRESS(0x812FDFEC);
extern volatile u8 g_unk_800030E2 AT_ADDRESS(0x800030E2);
/* 8033CDC0-8033D244 337700 0484+00 1/1 0/0 0/0 .text __OSBootDolSimple */
void __OSBootDolSimple(u32 doloffset, u32 restartCode, void* regionStart, void* regionEnd, BOOL argsUseDefault, s32 argc, char** argv) {

View File

@ -3,8 +3,8 @@
#include "dolphin/exi/EXIBios.h"
#include "dolphin/os.h"
vu32 __PIRegs[12] : 0xCC003000;
vu16 __MEMRegs[64] : 0xCC004000;
vu32 __PIRegs[12] AT_ADDRESS(0xCC003000);
vu16 __MEMRegs[64] AT_ADDRESS(0xCC004000);
/* 8033D6F4-8033D700 338034 000C+00 2/2 200/200 5/5 .text OSDisableInterrupts */
asm BOOL OSDisableInterrupts(void) {

View File

@ -4,7 +4,7 @@
#define TRUNC(n, a) (((u32)(n)) & ~((a)-1))
#define ROUND(n, a) (((u32)(n) + (a)-1) & ~((a)-1))
vu16 __MEMRegs[64] : 0xCC004000;
vu16 __MEMRegs[64] AT_ADDRESS(0xCC004000);
extern OSErrorHandlerEx __OSErrorTable[16];

View File

@ -1,7 +1,7 @@
#include "dolphin/os/OSReset.h"
#include "dolphin/os.h"
vu16 __VIRegs[59] : 0xCC002000;
vu16 __VIRegs[59] AT_ADDRESS(0xCC002000);
OSThreadQueue __OSActiveThreadQueue : (OS_BASE_CACHED | 0x00DC);
extern OSExecParams __OSRebootParams;

View File

@ -17,7 +17,7 @@ import os
import platform
import sys
from pathlib import Path
from typing import IO, Any, Dict, List, Optional, Set, Tuple, Union, cast
from typing import IO, Any, Dict, Iterable, List, Optional, Set, Tuple, cast
from . import ninja_syntax
from .ninja_syntax import serialize_path
@ -155,6 +155,9 @@ class ProjectConfig:
self.custom_build_steps: Optional[Dict[str, List[Dict[str, Any]]]] = (
None # Custom build steps, types are ["pre-compile", "post-compile", "post-link", "post-build"]
)
self.generate_compile_commands: bool = (
True # Generate compile_commands.json for clangd
)
# Progress output, progress.json and report.json config
self.progress = True # Enable progress output
@ -200,9 +203,40 @@ class ProjectConfig:
out[obj.name] = obj.resolve(self, lib)
return out
# Gets the output path for build-related files.
def out_path(self) -> Path:
return self.build_dir / str(self.version)
# Gets the path to the compilers directory.
# Exits the program if neither `compilers_path` nor `compilers_tag` is provided.
def compilers(self) -> Path:
if self.compilers_path:
return self.compilers_path
elif self.compilers_tag:
return self.build_dir / "compilers"
else:
sys.exit("ProjectConfig.compilers_tag missing")
# Gets the wrapper to use for compiler commands, if set.
def compiler_wrapper(self) -> Optional[Path]:
wrapper = self.wrapper
if self.use_wibo():
wrapper = self.build_dir / "tools" / "wibo"
if not is_windows() and wrapper is None:
wrapper = Path("wine")
return wrapper
# Determines whether or not to use wibo as the compiler wrapper.
def use_wibo(self) -> bool:
return (
self.wibo_tag is not None
and sys.platform == "linux"
and platform.machine() in ("i386", "x86_64")
and self.wrapper is None
)
def is_windows() -> bool:
return os.name == "nt"
@ -214,13 +248,26 @@ CHAIN = "cmd /c " if is_windows() else ""
EXE = ".exe" if is_windows() else ""
def make_flags_str(flags: Optional[Union[str, List[str]]]) -> str:
def file_is_asm(path: Path) -> bool:
return path.suffix.lower() == ".s"
def file_is_c(path: Path) -> bool:
return path.suffix.lower() == ".c"
def file_is_cpp(path: Path) -> bool:
return path.suffix.lower() in (".cc", ".cp", ".cpp", ".cxx")
def file_is_c_cpp(path: Path) -> bool:
return file_is_c(path) or file_is_cpp(path)
def make_flags_str(flags: Optional[List[str]]) -> str:
if flags is None:
return ""
elif isinstance(flags, list):
return " ".join(flags)
else:
return flags
return " ".join(flags)
# Load decomp-toolkit generated config.json
@ -253,13 +300,14 @@ def load_build_config(
return build_config
# Generate build.ninja and objdiff.json
# Generate build.ninja, objdiff.json and compile_commands.json
def generate_build(config: ProjectConfig) -> None:
config.validate()
objects = config.objects()
build_config = load_build_config(config, config.out_path() / "config.json")
generate_build_ninja(config, objects, build_config)
generate_objdiff_config(config, objects, build_config)
generate_compile_commands(config, objects, build_config)
# Generate build.ninja
@ -406,16 +454,10 @@ def generate_build_ninja(
else:
sys.exit("ProjectConfig.sjiswrap_tag missing")
wrapper = config.compiler_wrapper()
# Only add an implicit dependency on wibo if we download it
wrapper = config.wrapper
wrapper_implicit: Optional[Path] = None
if (
config.wibo_tag is not None
and sys.platform == "linux"
and platform.machine() in ("i386", "x86_64")
and config.wrapper is None
):
wrapper = build_tools_path / "wibo"
if wrapper is not None and config.use_wibo():
wrapper_implicit = wrapper
n.build(
outputs=wrapper,
@ -426,15 +468,11 @@ def generate_build_ninja(
"tag": config.wibo_tag,
},
)
if not is_windows() and wrapper is None:
wrapper = Path("wine")
wrapper_cmd = f"{wrapper} " if wrapper else ""
compilers = config.compilers()
compilers_implicit: Optional[Path] = None
if config.compilers_path:
compilers = config.compilers_path
elif config.compilers_tag:
compilers = config.build_dir / "compilers"
if config.compilers_path is None and config.compilers_tag is not None:
compilers_implicit = compilers
n.build(
outputs=compilers,
@ -445,8 +483,6 @@ def generate_build_ninja(
"tag": config.compilers_tag,
},
)
else:
sys.exit("ProjectConfig.compilers_tag missing")
binutils_implicit = None
if config.binutils_path:
@ -660,7 +696,6 @@ def generate_build_ninja(
n.comment(f"Link {self.name}")
if self.module_id == 0:
elf_path = build_path / f"{self.name}.elf"
dol_path = build_path / f"{self.name}.dol"
elf_ldflags = f"$ldflags -lcf {serialize_path(self.ldscript)}"
if config.generate_map:
elf_map = map_path(elf_path)
@ -725,17 +760,36 @@ def generate_build_ninja(
source_added: Set[Path] = set()
def c_build(obj: Object, src_path: Path) -> Optional[Path]:
cflags_str = make_flags_str(obj.options["cflags"])
if obj.options["extra_cflags"] is not None:
extra_cflags_str = make_flags_str(obj.options["extra_cflags"])
cflags_str += " " + extra_cflags_str
used_compiler_versions.add(obj.options["mw_version"])
# Avoid creating duplicate build rules
if obj.src_obj_path is None or obj.src_obj_path in source_added:
return obj.src_obj_path
source_added.add(obj.src_obj_path)
cflags = obj.options["cflags"]
extra_cflags = obj.options["extra_cflags"]
# Add appropriate language flag if it doesn't exist already
# Added directly to the source so it flows to other generation tasks
if not any(flag.startswith("-lang") for flag in cflags) and (
extra_cflags is None
or not any(flag.startswith("-lang") for flag in extra_cflags)
):
# Ensure extra_cflags is a unique instance,
# and insert into there to avoid modifying shared sets of flags
if extra_cflags is None:
extra_cflags = []
extra_cflags = obj.options["extra_cflags"] = list(extra_cflags)
if file_is_cpp(src_path):
extra_cflags.insert(0, "-lang=c++")
else:
extra_cflags.insert(0, "-lang=c")
cflags_str = make_flags_str(cflags)
if extra_cflags is not None:
extra_cflags_str = make_flags_str(extra_cflags)
cflags_str += " " + extra_cflags_str
used_compiler_versions.add(obj.options["mw_version"])
# Add MWCC build rule
lib_name = obj.options["lib"]
n.comment(f"{obj.name}: {lib_name} (linked {obj.completed})")
@ -767,7 +821,7 @@ def generate_build_ninja(
if obj.options["host"] and obj.host_obj_path is not None:
n.build(
outputs=obj.host_obj_path,
rule="host_cc" if src_path.suffix == ".c" else "host_cpp",
rule="host_cc" if file_is_c(src_path) else "host_cpp",
inputs=src_path,
variables={
"basedir": os.path.dirname(obj.host_obj_path),
@ -827,10 +881,10 @@ def generate_build_ninja(
link_built_obj = obj.completed
built_obj_path: Optional[Path] = None
if obj.src_path is not None and obj.src_path.exists():
if obj.src_path.suffix in (".c", ".cp", ".cpp"):
if file_is_c_cpp(obj.src_path):
# Add MWCC & host build rules
built_obj_path = c_build(obj, obj.src_path)
elif obj.src_path.suffix == ".s":
elif file_is_asm(obj.src_path):
# Add assembler build rule
built_obj_path = asm_build(obj, obj.src_path, obj.src_obj_path)
else:
@ -1274,30 +1328,20 @@ def generate_objdiff_config(
cflags = obj.options["cflags"]
reverse_fn_order = False
if type(cflags) is list:
for flag in cflags:
if not flag.startswith("-inline "):
continue
for value in flag.split(" ")[1].split(","):
if value == "deferred":
reverse_fn_order = True
elif value == "nodeferred":
reverse_fn_order = False
for flag in cflags:
if not flag.startswith("-inline "):
continue
for value in flag.split(" ")[1].split(","):
if value == "deferred":
reverse_fn_order = True
elif value == "nodeferred":
reverse_fn_order = False
# Filter out include directories
def keep_flag(flag):
return not flag.startswith("-i ") and not flag.startswith("-I ")
# Filter out include directories
def keep_flag(flag):
return not flag.startswith("-i ") and not flag.startswith("-I ")
cflags = list(filter(keep_flag, cflags))
# Add appropriate lang flag
if obj.src_path is not None and not any(
flag.startswith("-lang") for flag in cflags
):
if obj.src_path.suffix in (".cp", ".cpp"):
cflags.insert(0, "-lang=c++")
else:
cflags.insert(0, "-lang=c")
cflags = list(filter(keep_flag, cflags))
compiler_version = COMPILER_MAP.get(obj.options["mw_version"])
if compiler_version is None:
@ -1388,6 +1432,199 @@ def generate_objdiff_config(
json.dump(cleandict(objdiff_config), w, indent=2, default=unix_path)
def generate_compile_commands(
config: ProjectConfig,
objects: Dict[str, Object],
build_config: Optional[Dict[str, Any]],
) -> None:
if build_config is None or not config.generate_compile_commands:
return
# The following code attempts to convert mwcc flags to clang flags
# for use with clangd.
# Flags to ignore explicitly
CFLAG_IGNORE: Set[str] = {
# Search order modifier
# Has a different meaning to Clang, and would otherwise
# be picked up by the include passthrough prefix
"-I-",
"-i-",
}
CFLAG_IGNORE_PREFIX: Tuple[str, ...] = tuple()
# Flags to replace
CFLAG_REPLACE: Dict[str, str] = {}
CFLAG_REPLACE_PREFIX: Tuple[Tuple[str, str], ...] = (
# Includes
("-i ", "-I"),
("-I ", "-I"),
("-I+", "-I"),
# Defines
("-d ", "-D"),
("-D ", "-D"),
("-D+", "-D"),
)
# Flags with a finite set of options
CFLAG_REPLACE_OPTIONS: Tuple[Tuple[str, Dict[str, Tuple[str, ...]]], ...] = (
# Exceptions
(
"-Cpp_exceptions",
{
"off": ("-fno-cxx-exceptions",),
"on": ("-fcxx-exceptions",),
},
),
# RTTI
(
"-RTTI",
{
"off": ("-fno-rtti",),
"on": ("-frtti",),
},
),
# Language configuration
(
"-lang",
{
"c": ("--language=c", "--std=c89"),
"c99": ("--language=c", "--std=c99"),
"c++": ("--language=c++", "--std=c++98"),
"cplus": ("--language=c++", "--std=c++98"),
},
),
)
# Flags to pass through
CFLAG_PASSTHROUGH: Set[str] = set()
CFLAG_PASSTHROUGH_PREFIX: Tuple[str, ...] = (
"-I", # includes
"-D", # defines
)
clangd_config = []
def add_unit(build_obj: Dict[str, Any]) -> None:
obj = objects.get(build_obj["name"])
if obj is None:
return
# Skip unresolved objects
if (
obj.src_path is None
or obj.src_obj_path is None
or not file_is_c_cpp(obj.src_path)
):
return
# Gather cflags for source file
cflags: list[str] = []
def append_cflags(flags: Iterable[str]) -> None:
# Match a flag against either a set of concrete flags, or a set of prefixes.
def flag_match(
flag: str, concrete: Set[str], prefixes: Tuple[str, ...]
) -> bool:
if flag in concrete:
return True
for prefix in prefixes:
if flag.startswith(prefix):
return True
return False
# Determine whether a flag should be ignored.
def should_ignore(flag: str) -> bool:
return flag_match(flag, CFLAG_IGNORE, CFLAG_IGNORE_PREFIX)
# Determine whether a flag should be passed through.
def should_passthrough(flag: str) -> bool:
return flag_match(flag, CFLAG_PASSTHROUGH, CFLAG_PASSTHROUGH_PREFIX)
# Attempts replacement for the given flag.
def try_replace(flag: str) -> bool:
replacement = CFLAG_REPLACE.get(flag)
if replacement is not None:
cflags.append(replacement)
return True
for prefix, replacement in CFLAG_REPLACE_PREFIX:
if flag.startswith(prefix):
cflags.append(flag.replace(prefix, replacement, 1))
return True
for prefix, options in CFLAG_REPLACE_OPTIONS:
if not flag.startswith(prefix):
continue
# "-lang c99" and "-lang=c99" are both generally valid option forms
option = flag.removeprefix(prefix).removeprefix("=").lstrip()
replacements = options.get(option)
if replacements is not None:
cflags.extend(replacements)
return True
return False
for flag in flags:
# Ignore flags first
if should_ignore(flag):
continue
# Then find replacements
if try_replace(flag):
continue
# Pass flags through last
if should_passthrough(flag):
cflags.append(flag)
continue
append_cflags(obj.options["cflags"])
if isinstance(obj.options["extra_cflags"], list):
append_cflags(obj.options["extra_cflags"])
unit_config = {
"directory": Path.cwd(),
"file": obj.src_path,
"output": obj.src_obj_path,
"arguments": [
"clang",
"-nostdinc",
"-fno-builtin",
"--target=powerpc-eabi",
*cflags,
"-c",
obj.src_path,
"-o",
obj.src_obj_path,
],
}
clangd_config.append(unit_config)
# Add DOL units
for unit in build_config["units"]:
add_unit(unit)
# Add REL units
for module in build_config["modules"]:
for unit in module["units"]:
add_unit(unit)
# Write compile_commands.json
with open("compile_commands.json", "w", encoding="utf-8") as w:
def default_format(o):
if isinstance(o, Path):
return o.resolve().as_posix()
return str(o)
json.dump(clangd_config, w, indent=2, default=default_format)
# Calculate, print and write progress to progress.json
def calculate_progress(config: ProjectConfig) -> None:
config.validate()