diff --git a/.gitignore b/.gitignore index 3fb0501a..83345ae1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1,31 @@ -__pycache__/ -*.dol -*.dump -*.exe -*.map -build/ -ctx.c -tools/elf2dol -tools/elf2rel -tools/metroidbuildinfo -tools/mwcc_compiler -*.bat +# IDE folders .idea/ -versions/ -build.ninja -.ninja_deps -.ninja_lock -.ninja_log -objdiff.json +.vs/ +.vscode/ + +# Caches +__pycache__ +.mypy_cache +.cache/ + +# Original files orig/*/* !orig/*/.gitkeep + +# Build files +build/ +.ninja_* +build.ninja + +# decompctx output +ctx.* +*.ctx + +# Generated configs +objdiff.json +compile_commands.json + +# Miscellaneous +/*.txt +*.exe +*.dol diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json deleted file mode 100644 index b02ae17c..00000000 --- a/.vscode/c_cpp_properties.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "configurations": [ - { - "name": "Linux", - "includePath": [ - "${workspaceFolder}/include", - "${workspaceFolder}/libc", - "${workspaceFolder}/extern/musyx/include", - "${workspaceFolder}/build/*/include" - ], - "cStandard": "c99", - "cppStandard": "c++98", - "intelliSenseMode": "linux-clang-x86", - "compilerPath": "", - "configurationProvider": "ms-vscode.makefile-tools", - "browse": { - "path": [ - "${workspaceFolder}/include", - "${workspaceFolder}/libc" - ], - "limitSymbolsToIncludedHeaders": true - } - } - ], - "version": 4 -} diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 8530d405..f6e73f3e 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,7 +1,13 @@ { "recommendations": [ - "ms-vscode.cpptools", "akiramiyakoda.cppincludeguard", - "ms-python.black-formatter" + "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 index fff8716d..46620bd2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,27 +1,21 @@ { "[c]": { "files.encoding": "utf8", - "editor.defaultFormatter": "ms-vscode.cpptools" + "editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd" }, "[cpp]": { "files.encoding": "utf8", - "editor.defaultFormatter": "ms-vscode.cpptools" + "editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd" }, "[python]": { "editor.defaultFormatter": "ms-python.black-formatter" }, - "clangd.arguments": ["--header-insertion=never"], - "clangd.fallbackFlags": [ - "-I${workspaceFolder}/include", - "-I${workspaceFolder}/libc", - "-I${workspaceFolder}/extern/musyx/include", - "-D__MWERKS__", - ], "editor.tabSize": 2, "files.associations": { "source_location": "cpp", "*.h": "c", - "*.inc": "c" + "*.inc": "c", + ".clangd": "yaml", }, "files.autoSave": "onFocusChange", "files.insertFinalNewline": true, @@ -50,5 +44,7 @@ "C/C++ Include Guard.Auto Update Path Blocklist": [ "include/zlib" ], - "cmake.configureOnOpen": false + "cmake.configureOnOpen": false, + // Disable C++ intellisense engine, use clangd instead + "C_Cpp.intelliSenseEngine": "disabled" } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 6da70938..c2c3e523 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,40 +1,12 @@ { - // See https://go.microsoft.com/fwlink/?LinkId=733558 - // for the documentation about the tasks.json format + // 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": false - } - }, - { - "label": "all_source", - "type": "shell", - "command": "ninja all_source", - "group": "build", - "problemMatcher": [] - }, - { - "label": "all_source_host", - "type": "shell", - "command": "ninja all_source_host", - "problemMatcher": { - "base": "$gcc" - }, - "group": "build" - }, - { - "label": "current file (host)", - "type": "shell", - "command": "ninja host/${relativeFile}", - "problemMatcher": { - "base": "$gcc" - }, "group": { "kind": "build", "isDefault": true diff --git a/include/Kyoto/Alloc/CMemory.hpp b/include/Kyoto/Alloc/CMemory.hpp index fdd47863..e3a2a44b 100644 --- a/include/Kyoto/Alloc/CMemory.hpp +++ b/include/Kyoto/Alloc/CMemory.hpp @@ -18,7 +18,7 @@ public: static void* Alloc(size_t len, IAllocator::EHint hint = IAllocator::kHI_None, IAllocator::EScope scope = IAllocator::kSC_Unk1, IAllocator::EType type = IAllocator::kTP_Heap, - const CCallStack& callstack = CCallStack(-1, "??(??)")); + const CCallStack& callstack = CCallStack(-1, "\?\?(\?\?)")); static void Free(const void* ptr); static void SetOutOfMemoryCallback(IAllocator::FOutOfMemoryCb callback, const void* context); static void OffsetFakeStatics(int); @@ -32,7 +32,7 @@ void* operator new(size_t sz, const char*, const char*); void* operator new[](size_t sz, const char*, const char*); inline void operator delete(void* ptr) { CMemory::Free(ptr); } inline void operator delete[](void* ptr) { CMemory::Free(ptr); } -#define rs_new new ("??(??)", nullptr) +#define rs_new new ("\?\?(\?\?)", nullptr) #else void operator delete(void* ptr); void operator delete[](void* ptr); diff --git a/include/Kyoto/Particles/IElement.hpp b/include/Kyoto/Particles/IElement.hpp index 1e9523d3..1f261fcc 100644 --- a/include/Kyoto/Particles/IElement.hpp +++ b/include/Kyoto/Particles/IElement.hpp @@ -15,7 +15,7 @@ public: // -> CFrameDelayedKiller void* operator new(size_t sz, const char*, const char*); - void* operator new(size_t sz) { return operator new(sz, "??(??)", nullptr); } + void* operator new(size_t sz) { return operator new(sz, "\?\?(\?\?)", nullptr); } void operator delete(void* ptr, size_t sz); }; diff --git a/include/Kyoto/TOneStatic.hpp b/include/Kyoto/TOneStatic.hpp index fbe6bb85..f5439672 100644 --- a/include/Kyoto/TOneStatic.hpp +++ b/include/Kyoto/TOneStatic.hpp @@ -8,7 +8,7 @@ template < typename T > class TOneStatic { public: void* operator new(size_t sz, const char*, const char*); - void* operator new(size_t sz) { return operator new(sz, "??(??)", nullptr); } + void* operator new(size_t sz) { return operator new(sz, "\?\?(\?\?)", nullptr); } void operator delete(void* ptr); private: diff --git a/include/dolphin/types.h b/include/dolphin/types.h index a79ff451..7ddd6a60 100644 --- a/include/dolphin/types.h +++ b/include/dolphin/types.h @@ -65,18 +65,25 @@ typedef int BOOL; #define NULL 0 #endif #endif + #if !defined(__cplusplus) || __cplusplus < 201103L +// Define nullptr as NULL #ifndef nullptr #define nullptr NULL #endif +#endif // !defined(__cplusplus) || __cplusplus < 201103L -#if defined(__MWERKS__) +#if defined(__cplusplus) && __cplusplus < 201103L +#if defined(__clang__) +// Allow override in < C++11 mode with clangd +#pragma clang diagnostic ignored "-Wc++11-extensions" +#else +// Define override as nothing #ifndef override #define override #endif -#endif - -#endif +#endif // defined(__clang__) +#endif // defined(__cplusplus) && __cplusplus < 201103L #ifndef ATTRIBUTE_ALIGN #if defined(__MWERKS__) || defined(__GNUC__) diff --git a/include/rstl/list.hpp b/include/rstl/list.hpp index c0dd1750..4f94330e 100644 --- a/include/rstl/list.hpp +++ b/include/rstl/list.hpp @@ -136,11 +136,11 @@ public: return *this; } iterator operator--(int) { return iterator(this->curent->x0_prev); } - T* get_pointer() const { return current->get_value(); } - T& operator*() const { return *current->get_value(); } - T* operator->() const { return current->get_value(); } - bool operator==(const iterator& other) const { return current == other.current; } - bool operator!=(const iterator& other) const { return current != other.current; } + T* get_pointer() const { return this->current->get_value(); } + T& operator*() const { return *this->current->get_value(); } + T* operator->() const { return this->current->get_value(); } + bool operator==(const iterator& other) const { return this->current == other.current; } + bool operator!=(const iterator& other) const { return this->current != other.current; } }; private: diff --git a/include/rstl/pointer_iterator.hpp b/include/rstl/pointer_iterator.hpp index 9705f24a..58864930 100644 --- a/include/rstl/pointer_iterator.hpp +++ b/include/rstl/pointer_iterator.hpp @@ -101,10 +101,10 @@ public: return *this; } pointer_iterator operator+(int v) const { - return pointer_iterator(current + v); + return pointer_iterator(this->current + v); } pointer_iterator operator-(int v) const { - return pointer_iterator(current - v); + return pointer_iterator(this->current - v); } // HACK: non-const operator- is required to match vector::insert difference_type operator-(const pointer_iterator& other) { return this->current - other.current; } diff --git a/include/rstl/single_ptr.hpp b/include/rstl/single_ptr.hpp index bbb823e6..edbcbadd 100644 --- a/include/rstl/single_ptr.hpp +++ b/include/rstl/single_ptr.hpp @@ -52,7 +52,7 @@ single_ptr< T >& single_ptr< T >::Set(T* ptr) { return *this = ptr; } -typedef single_ptr< void > unk_singleptr; +typedef single_ptr< int > unk_singleptr; CHECK_SIZEOF(unk_singleptr, 0x4); } // namespace rstl diff --git a/include/rstl/vector.hpp b/include/rstl/vector.hpp index 25abc99f..5765da49 100644 --- a/include/rstl/vector.hpp +++ b/include/rstl/vector.hpp @@ -233,7 +233,7 @@ typename vector< T, Alloc >::iterator vector< T, Alloc >::erase(iterator first, return first; } -typedef vector< void > unk_vector; +typedef vector< int > unk_vector; CHECK_SIZEOF(unk_vector, 0x10) } // namespace rstl diff --git a/include/static_assert.hpp b/include/static_assert.hpp index 51977dea..404fe22e 100644 --- a/include/static_assert.hpp +++ b/include/static_assert.hpp @@ -1,5 +1,6 @@ // C++98 static assert +#ifdef __MWERKS__ struct false_type { static const int value = 0; }; @@ -17,7 +18,6 @@ struct _n_is_equal< A, A > : true_type {}; template < class T, int N > struct check_sizeof : _n_is_equal< sizeof(T), N > {}; -#ifdef __MWERKS__ #ifndef offsetof typedef unsigned long size_t; #define offsetof(type, member) ((size_t) & (((type*)0)->member)) @@ -26,6 +26,11 @@ typedef unsigned long size_t; #define NESTED_CHECK_SIZEOF(parent, cls, size) extern int cls##_check[check_sizeof< parent::cls, size >::value]; #define CHECK_OFFSETOF(cls, member, offset) \ extern int cls##_check_offset##[_n_is_equal< offsetof(cls, member), offset >::value]; +#elif defined(__clang__) && defined(__powerpc__) // Enable for clangd +#pragma clang diagnostic ignored "-Wc++17-extensions" // Allow _Static_assert without message +#define CHECK_SIZEOF(cls, size) _Static_assert(sizeof(cls) == size); +#define NESTED_CHECK_SIZEOF(parent, cls, size) _Static_assert(sizeof(parent::cls) == size); +#define CHECK_OFFSETOF(cls, member, offset) _Static_assert(offsetof(cls, member) == offset); #else #define CHECK_SIZEOF(cls, size) #define NESTED_CHECK_SIZEOF(parent, cls, size) diff --git a/libc/math.h b/libc/math.h index a732edab..9d8ce973 100644 --- a/libc/math.h +++ b/libc/math.h @@ -164,6 +164,7 @@ static inline int __fpclassifyd(double x) { #define isinf(x) (fpclassify(x) == FP_INFINITE) #define isfinite(x) ((fpclassify(x) > FP_INFINITE)) +#ifdef __MWERKS__ extern inline float sqrtf(float x) { static const double _half = .5; static const double _three = 3.0; @@ -198,6 +199,10 @@ _MATH_INLINE double sqrt(double x) { } return INFINITY; } +#else +float sqrtf(float x); +double sqrt(double x); +#endif static inline float ldexpf(float x, int exp) { return (float)ldexp((double)x, exp); } static inline double scalbn(double x, int n) { return ldexp(x, n); } diff --git a/src/Kyoto/CARAMManager.cpp b/src/Kyoto/CARAMManager.cpp index 52e0a4db..f50212bf 100644 --- a/src/Kyoto/CARAMManager.cpp +++ b/src/Kyoto/CARAMManager.cpp @@ -23,7 +23,7 @@ bool CARAMManager::Initialize(uint chunkSize) { mpARAMStart = ARAlloc(chunkSize * numChunks); mpBookKeepingMemory = (uint*)CMemory::Alloc(numChunks * 4, IAllocator::kHI_None, IAllocator::kSC_Unk1, IAllocator::kTP_Heap, - CCallStack(-1, "??(??)")); + CCallStack(-1, "\?\?(\?\?)")); CMemory::OffsetFakeStatics(mNumChunks * 4); for (uint i = 0; i < numChunks; ++i) { diff --git a/src/MetroidPrime/Decode.cpp b/src/MetroidPrime/Decode.cpp index 35eea637..e0d85969 100644 --- a/src/MetroidPrime/Decode.cpp +++ b/src/MetroidPrime/Decode.cpp @@ -48,11 +48,11 @@ CArchitectureMessage MakeMsg::CreateControllerStatus(EArchMsgTarget target, cons } CArchitectureMessage MakeMsg::CreateQuitGameplay(EArchMsgTarget target) { - return CArchitectureMessage(target, kAM_QuitGameplay, new("??(??)", nullptr) CArchMsgParmNull()); + return CArchitectureMessage(target, kAM_QuitGameplay, rs_new CArchMsgParmNull()); } CArchitectureMessage MakeMsg::CreateFrameBegin(EArchMsgTarget target, const int& a) { - return CArchitectureMessage(target, kAM_FrameBegin, new("??(??)", nullptr) CArchMsgParmInt32(a)); + return CArchitectureMessage(target, kAM_FrameBegin, rs_new CArchMsgParmInt32(a)); } const CArchMsgParmInt32& MakeMsg::GetParmFrameBegin(const CArchitectureMessage& msg) { @@ -60,5 +60,5 @@ const CArchMsgParmInt32& MakeMsg::GetParmFrameBegin(const CArchitectureMessage& } CArchitectureMessage MakeMsg::CreateFrameEnd(EArchMsgTarget target, const int& a) { - return CArchitectureMessage(target, kAM_FrameEnd, new("??(??)", nullptr) CArchMsgParmInt32(a)); + return CArchitectureMessage(target, kAM_FrameEnd, rs_new CArchMsgParmInt32(a)); } diff --git a/src/MetroidPrime/Player/CPlayer.cpp b/src/MetroidPrime/Player/CPlayer.cpp index 9818905b..f0ae30fe 100644 --- a/src/MetroidPrime/Player/CPlayer.cpp +++ b/src/MetroidPrime/Player/CPlayer.cpp @@ -772,10 +772,11 @@ void CPlayer::Update(float dt, CStateManager& mgr) { switch (x2f8_morphBallState) { case kMS_Unmorphed: case kMS_Morphing: - case kMS_Unmorphing: + case kMS_Unmorphing: { CTransform4f gunXf = GetModelData()->GetScaledLocatorTransform(rstl::string_l(kGunLocator)); x7f4_gunWorldXf = GetTransform() * gunXf; break; + } case kMS_Morphed: break; } @@ -2005,7 +2006,8 @@ void CPlayer::UpdateFreeLook(float dt) { } angleVelP = x3e8_horizFreeLookAngleVel - x3e4_freeLookYawAngle; - dx = lookDeltaAngle * CMath::Clamp(0.f, fabsf(angleVelP / gpTweakPlayer->GetHorizontalFreeLookAngleVel()), 1.f); + dx = lookDeltaAngle * + CMath::Clamp(0.f, fabsf(angleVelP / gpTweakPlayer->GetHorizontalFreeLookAngleVel()), 1.f); if (0.f <= angleVelP) { x3e4_freeLookYawAngle += dx; } else { diff --git a/src/MetroidPrime/main.cpp b/src/MetroidPrime/main.cpp index 302ba287..4762af6d 100644 --- a/src/MetroidPrime/main.cpp +++ b/src/MetroidPrime/main.cpp @@ -87,7 +87,7 @@ const char* s1 = "MiscSamus_AGSC"; const char* s2 = "UI_AGSC"; const char* s3 = "Weapons_AGSC"; const char* s4 = "ZZZ_AGSC"; -const char* s5 = "??(??)"; +const char* s5 = "\?\?(\?\?)"; const char* s6 = ""; const char* s7 = "%d"; const char* st8 = ".pak"; diff --git a/tools/download_tool.py b/tools/download_tool.py index 69ef96a7..f4512d01 100644 --- a/tools/download_tool.py +++ b/tools/download_tool.py @@ -55,6 +55,7 @@ def dtk_url(tag: str) -> str: repo = "https://github.com/encounter/decomp-toolkit" return f"{repo}/releases/download/{tag}/dtk-{system}-{arch}{suffix}" + def objdiff_cli_url(tag: str) -> str: uname = platform.uname() suffix = "" diff --git a/tools/project.py b/tools/project.py index cee55835..852d4a77 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-none-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()