diff --git a/.gitignore b/.gitignore index 47e8153c00a..275db7547fb 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000000..c20797ff584 --- /dev/null +++ b/.vscode/extensions.json @@ -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", + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000000..0428af30445 --- /dev/null +++ b/.vscode/settings.json @@ -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", +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000000..6dc12130bc3 --- /dev/null +++ b/.vscode/tasks.json @@ -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 + } + }, + ] +} diff --git a/include/JSystem/JGadget/linklist.h b/include/JSystem/JGadget/linklist.h index bb334f57f9b..70a15a12763 100644 --- a/include/JSystem/JGadget/linklist.h +++ b/include/JSystem/JGadget/linklist.h @@ -249,9 +249,9 @@ struct TLinkList_factory : public TLinkList { 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 -struct TContainerEnumerator : public TEnumerator2::iterator, T> { +struct TContainerEnumerator : public TEnumerator2::iterator, T> { inline TContainerEnumerator(TLinkList* param_0) - : TEnumerator2::iterator, T>(param_0->begin(), param_0->end()) {} + : TEnumerator2::iterator, T>(param_0->begin(), param_0->end()) {} }; template -struct TContainerEnumerator_const : public TEnumerator2::const_iterator, const T> { +struct TContainerEnumerator_const : public TEnumerator2::const_iterator, const T> { inline TContainerEnumerator_const(const TLinkList* param_0) - : TEnumerator2::const_iterator, const T>(param_0->begin(), param_0->end()) {} + : TEnumerator2::const_iterator, const T>(param_0->begin(), param_0->end()) {} }; }; // namespace JGadget diff --git a/include/dol2asm.h b/include/dol2asm.h index 86665c181d3..3b28a7740e2 100644 --- a/include/dol2asm.h +++ b/include/dol2asm.h @@ -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") diff --git a/include/dolphin/dsp.h b/include/dolphin/dsp.h index 7e676489b19..bf091c414cf 100644 --- a/include/dolphin/dsp.h +++ b/include/dolphin/dsp.h @@ -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 diff --git a/include/dolphin/dvd/dvdlow.h b/include/dolphin/dvd/dvdlow.h index a7645d901ce..1d707578053 100644 --- a/include/dolphin/dvd/dvdlow.h +++ b/include/dolphin/dvd/dvdlow.h @@ -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 */ diff --git a/include/dolphin/exi/EXIBios.h b/include/dolphin/exi/EXIBios.h index 00099f62b17..3e7178e2752 100644 --- a/include/dolphin/exi/EXIBios.h +++ b/include/dolphin/exi/EXIBios.h @@ -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 diff --git a/include/dolphin/gx.h b/include/dolphin/gx.h index 12d80c77cbf..8dc15f688ea 100644 --- a/include/dolphin/gx.h +++ b/include/dolphin/gx.h @@ -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; diff --git a/include/dolphin/os.h b/include/dolphin/os.h index 4d20b105f04..dce02c47c3c 100644 --- a/include/dolphin/os.h +++ b/include/dolphin/os.h @@ -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); diff --git a/include/dolphin/os/OSContext.h b/include/dolphin/os/OSContext.h index 78562a353de..4137d90b2c2 100644 --- a/include/dolphin/os/OSContext.h +++ b/include/dolphin/os/OSContext.h @@ -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); diff --git a/include/dolphin/os/OSLink.h b/include/dolphin/os/OSLink.h index 55e63d5e2f1..311cc0090f6 100644 --- a/include/dolphin/os/OSLink.h +++ b/include/dolphin/os/OSLink.h @@ -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; diff --git a/include/dolphin/os/OSReset.h b/include/dolphin/os/OSReset.h index 54678e53d2c..754d5173618 100644 --- a/include/dolphin/os/OSReset.h +++ b/include/dolphin/os/OSReset.h @@ -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 diff --git a/include/dolphin/os/OSThread.h b/include/dolphin/os/OSThread.h index 4b1b6cb72c6..68061c6e830 100644 --- a/include/dolphin/os/OSThread.h +++ b/include/dolphin/os/OSThread.h @@ -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); diff --git a/include/dolphin/os/OSTime.h b/include/dolphin/os/OSTime.h index c0df3585fa7..3cd474bf300 100644 --- a/include/dolphin/os/OSTime.h +++ b/include/dolphin/os/OSTime.h @@ -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) diff --git a/include/dolphin/si/SIBios.h b/include/dolphin/si/SIBios.h index 1ea637b7fad..3e6c48acd90 100644 --- a/include/dolphin/si/SIBios.h +++ b/include/dolphin/si/SIBios.h @@ -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 } diff --git a/include/dolphin/types.h b/include/dolphin/types.h index b72532c1183..dc06598ce51 100644 --- a/include/dolphin/types.h +++ b/include/dolphin/types.h @@ -42,4 +42,10 @@ typedef int BOOL; #define FLOAT_MIN (1.175494351e-38f) #define FLOAT_MAX (3.40282346638528860e+38f) -#endif \ No newline at end of file +#ifdef __MWERKS__ +#define AT_ADDRESS(xyz) : (xyz) +#else +#define AT_ADDRESS(xyz) +#endif + +#endif diff --git a/include/dolphin/vi.h b/include/dolphin/vi.h index c30e6f3a559..9c7712e7469 100644 --- a/include/dolphin/vi.h +++ b/include/dolphin/vi.h @@ -138,7 +138,7 @@ u32 VIGetRetraceCount(); u32 VIGetDTVStatus(); u32 VIGetTvFormat(void); -vu16 __VIRegs[59] : 0xCC002000; +vu16 __VIRegs[59] AT_ADDRESS(0xCC002000); #ifdef __cplusplus }; diff --git a/include/global.h b/include/global.h index cab5763ef90..cc16a8a5047 100644 --- a/include/global.h +++ b/include/global.h @@ -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] diff --git a/src/PowerPC_EABI_Support/MSL/MSL_C/MSL_Common/Include/stddef.h b/src/PowerPC_EABI_Support/MSL/MSL_C/MSL_Common/Include/stddef.h index dacf399772e..7a3e963fccc 100644 --- a/src/PowerPC_EABI_Support/MSL/MSL_C/MSL_Common/Include/stddef.h +++ b/src/PowerPC_EABI_Support/MSL/MSL_C/MSL_Common/Include/stddef.h @@ -23,4 +23,4 @@ typedef long ptrdiff_t; }; #endif -#endif \ No newline at end of file +#endif diff --git a/src/PowerPC_EABI_Support/Runtime/Inc/__va_arg.h b/src/PowerPC_EABI_Support/Runtime/Inc/__va_arg.h index 5d55401291a..22975640212 100644 --- a/src/PowerPC_EABI_Support/Runtime/Inc/__va_arg.h +++ b/src/PowerPC_EABI_Support/Runtime/Inc/__va_arg.h @@ -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 @@ -37,4 +37,4 @@ void* __va_arg(_va_list_struct*, int); #define __va_copy(a, b) (*(a) = *(b)) -#endif /* __VA_ARG_H */ \ No newline at end of file +#endif /* __VA_ARG_H */ diff --git a/src/dolphin/card/CARDMount.c b/src/dolphin/card/CARDMount.c index bdc8a7176c6..0b0c200a1a0 100644 --- a/src/dolphin/card/CARDMount.c +++ b/src/dolphin/card/CARDMount.c @@ -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); @@ -395,4 +395,4 @@ s32 CARDUnmount(s32 chan) { } DoUnmount(chan, CARD_RESULT_NOCARD); return CARD_RESULT_READY; -} \ No newline at end of file +} diff --git a/src/dolphin/os/OS.c b/src/dolphin/os/OS.c index 8e54083b99d..eff7103adbb 100644 --- a/src/dolphin/os/OS.c +++ b/src/dolphin/os/OS.c @@ -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 @@ -690,4 +690,4 @@ u32 __OSGetDIConfig(void) { /* 8033A874-8033A8A0 3351B4 002C+00 1/1 11/11 0/0 .text OSRegisterVersion */ void OSRegisterVersion(const char* version) { OSReport("%s\n", version); -} \ No newline at end of file +} diff --git a/src/dolphin/os/OSExec.c b/src/dolphin/os/OSExec.c index 07c7c4da8ee..1f20351ec81 100644 --- a/src/dolphin/os/OSExec.c +++ b/src/dolphin/os/OSExec.c @@ -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) { @@ -312,4 +312,4 @@ void __OSBootDol(u32 doloffset, u32 restartCode, const char** argv) { } __OSBootDolSimple(-1, restartCode, saveStart, saveEnd, FALSE, argvlen, argvToPass); -} \ No newline at end of file +} diff --git a/src/dolphin/os/OSInterrupt.c b/src/dolphin/os/OSInterrupt.c index 6c0b8b22bf2..1168572aff3 100644 --- a/src/dolphin/os/OSInterrupt.c +++ b/src/dolphin/os/OSInterrupt.c @@ -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) { @@ -445,4 +445,4 @@ static asm void ExternalInterruptHandler(register __OSInterrupt type, register O stwu r1, -8(r1) b __OSDispatchInterrupt // clang-format on -} \ No newline at end of file +} diff --git a/src/dolphin/os/OSMemory.c b/src/dolphin/os/OSMemory.c index 5e3854e4518..a9eb77848b3 100644 --- a/src/dolphin/os/OSMemory.c +++ b/src/dolphin/os/OSMemory.c @@ -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]; @@ -216,4 +216,4 @@ void __OSInitMemoryProtection() { __OSUnmaskInterrupts(OS_INTERRUPTMASK_MEM_ADDRESS); OSRestoreInterrupts(enabled); -} \ No newline at end of file +} diff --git a/src/dolphin/os/OSReset.c b/src/dolphin/os/OSReset.c index c28691601f0..c9a5054e604 100644 --- a/src/dolphin/os/OSReset.c +++ b/src/dolphin/os/OSReset.c @@ -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; @@ -217,4 +217,4 @@ u32 OSGetResetCode(void) { return 0x80000000 | __OSRebootParams.restartCode; return ((__PIRegs[9] & ~7) >> 3); -} \ No newline at end of file +} diff --git a/tools/project.py b/tools/project.py index cee5583596e..1d9c79bedd9 100644 --- a/tools/project.py +++ b/tools/project.py @@ -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()