Enable clangd integration & revamp VS Code config

This commit is contained in:
Luke Street 2024-10-12 16:58:00 -06:00
parent 5eb6264efd
commit 8768673d18
21 changed files with 382 additions and 167 deletions

46
.gitignore vendored
View File

@ -1,21 +1,31 @@
__pycache__/ # IDE folders
*.dol
*.dump
*.exe
*.map
build/
ctx.c
tools/elf2dol
tools/elf2rel
tools/metroidbuildinfo
tools/mwcc_compiler
*.bat
.idea/ .idea/
versions/ .vs/
build.ninja .vscode/
.ninja_deps
.ninja_lock # Caches
.ninja_log __pycache__
objdiff.json .mypy_cache
.cache/
# Original files
orig/*/* orig/*/*
!orig/*/.gitkeep !orig/*/.gitkeep
# Build files
build/
.ninja_*
build.ninja
# decompctx output
ctx.*
*.ctx
# Generated configs
objdiff.json
compile_commands.json
# Miscellaneous
/*.txt
*.exe
*.dol

View File

@ -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
}

View File

@ -1,7 +1,13 @@
{ {
"recommendations": [ "recommendations": [
"ms-vscode.cpptools",
"akiramiyakoda.cppincludeguard", "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",
] ]
} }

18
.vscode/settings.json vendored
View File

@ -1,27 +1,21 @@
{ {
"[c]": { "[c]": {
"files.encoding": "utf8", "files.encoding": "utf8",
"editor.defaultFormatter": "ms-vscode.cpptools" "editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd"
}, },
"[cpp]": { "[cpp]": {
"files.encoding": "utf8", "files.encoding": "utf8",
"editor.defaultFormatter": "ms-vscode.cpptools" "editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd"
}, },
"[python]": { "[python]": {
"editor.defaultFormatter": "ms-python.black-formatter" "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, "editor.tabSize": 2,
"files.associations": { "files.associations": {
"source_location": "cpp", "source_location": "cpp",
"*.h": "c", "*.h": "c",
"*.inc": "c" "*.inc": "c",
".clangd": "yaml",
}, },
"files.autoSave": "onFocusChange", "files.autoSave": "onFocusChange",
"files.insertFinalNewline": true, "files.insertFinalNewline": true,
@ -50,5 +44,7 @@
"C/C++ Include Guard.Auto Update Path Blocklist": [ "C/C++ Include Guard.Auto Update Path Blocklist": [
"include/zlib" "include/zlib"
], ],
"cmake.configureOnOpen": false "cmake.configureOnOpen": false,
// Disable C++ intellisense engine, use clangd instead
"C_Cpp.intelliSenseEngine": "disabled"
} }

32
.vscode/tasks.json vendored
View File

@ -1,40 +1,12 @@
{ {
// See https://go.microsoft.com/fwlink/?LinkId=733558 // Use Ctrl+Shift+B to run build tasks.
// for the documentation about the tasks.json format // Or "Run Build Task" in the Command Palette.
"version": "2.0.0", "version": "2.0.0",
"tasks": [ "tasks": [
{ {
"label": "ninja", "label": "ninja",
"type": "shell", "type": "shell",
"command": "ninja", "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": { "group": {
"kind": "build", "kind": "build",
"isDefault": true "isDefault": true

View File

@ -18,7 +18,7 @@ public:
static void* Alloc(size_t len, IAllocator::EHint hint = IAllocator::kHI_None, static void* Alloc(size_t len, IAllocator::EHint hint = IAllocator::kHI_None,
IAllocator::EScope scope = IAllocator::kSC_Unk1, IAllocator::EScope scope = IAllocator::kSC_Unk1,
IAllocator::EType type = IAllocator::kTP_Heap, IAllocator::EType type = IAllocator::kTP_Heap,
const CCallStack& callstack = CCallStack(-1, "??(??)")); const CCallStack& callstack = CCallStack(-1, "\?\?(\?\?)"));
static void Free(const void* ptr); static void Free(const void* ptr);
static void SetOutOfMemoryCallback(IAllocator::FOutOfMemoryCb callback, const void* context); static void SetOutOfMemoryCallback(IAllocator::FOutOfMemoryCb callback, const void* context);
static void OffsetFakeStatics(int); 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*); 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); }
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 #else
void operator delete(void* ptr); void operator delete(void* ptr);
void operator delete[](void* ptr); void operator delete[](void* ptr);

View File

@ -15,7 +15,7 @@ public:
// -> CFrameDelayedKiller // -> CFrameDelayedKiller
void* operator new(size_t sz, const char*, const char*); 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); void operator delete(void* ptr, size_t sz);
}; };

View File

@ -8,7 +8,7 @@ template < typename T >
class TOneStatic { class TOneStatic {
public: public:
void* operator new(size_t sz, const char*, const char*); 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); void operator delete(void* ptr);
private: private:

View File

@ -65,18 +65,25 @@ typedef int BOOL;
#define NULL 0 #define NULL 0
#endif #endif
#endif #endif
#if !defined(__cplusplus) || __cplusplus < 201103L #if !defined(__cplusplus) || __cplusplus < 201103L
// Define nullptr as NULL
#ifndef nullptr #ifndef nullptr
#define nullptr NULL #define nullptr NULL
#endif #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 #ifndef override
#define override #define override
#endif #endif
#endif #endif // defined(__clang__)
#endif // defined(__cplusplus) && __cplusplus < 201103L
#endif
#ifndef ATTRIBUTE_ALIGN #ifndef ATTRIBUTE_ALIGN
#if defined(__MWERKS__) || defined(__GNUC__) #if defined(__MWERKS__) || defined(__GNUC__)

View File

@ -136,11 +136,11 @@ public:
return *this; return *this;
} }
iterator operator--(int) { return iterator(this->curent->x0_prev); } iterator operator--(int) { return iterator(this->curent->x0_prev); }
T* get_pointer() const { return current->get_value(); } T* get_pointer() const { return this->current->get_value(); }
T& operator*() const { return *current->get_value(); } T& operator*() const { return *this->current->get_value(); }
T* operator->() const { return current->get_value(); } T* operator->() const { return this->current->get_value(); }
bool operator==(const iterator& other) const { return current == other.current; } bool operator==(const iterator& other) const { return this->current == other.current; }
bool operator!=(const iterator& other) const { return current != other.current; } bool operator!=(const iterator& other) const { return this->current != other.current; }
}; };
private: private:

View File

@ -101,10 +101,10 @@ public:
return *this; return *this;
} }
pointer_iterator operator+(int v) const { pointer_iterator operator+(int v) const {
return pointer_iterator(current + v); return pointer_iterator(this->current + v);
} }
pointer_iterator operator-(int v) const { 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 // HACK: non-const operator- is required to match vector::insert
difference_type operator-(const pointer_iterator& other) { return this->current - other.current; } difference_type operator-(const pointer_iterator& other) { return this->current - other.current; }

View File

@ -52,7 +52,7 @@ single_ptr< T >& single_ptr< T >::Set(T* ptr) {
return *this = ptr; return *this = ptr;
} }
typedef single_ptr< void > unk_singleptr; typedef single_ptr< int > unk_singleptr;
CHECK_SIZEOF(unk_singleptr, 0x4); CHECK_SIZEOF(unk_singleptr, 0x4);
} // namespace rstl } // namespace rstl

View File

@ -233,7 +233,7 @@ typename vector< T, Alloc >::iterator vector< T, Alloc >::erase(iterator first,
return first; return first;
} }
typedef vector< void > unk_vector; typedef vector< int > unk_vector;
CHECK_SIZEOF(unk_vector, 0x10) CHECK_SIZEOF(unk_vector, 0x10)
} // namespace rstl } // namespace rstl

View File

@ -1,5 +1,6 @@
// C++98 static assert // C++98 static assert
#ifdef __MWERKS__
struct false_type { struct false_type {
static const int value = 0; static const int value = 0;
}; };
@ -17,7 +18,6 @@ struct _n_is_equal< A, A > : true_type {};
template < class T, int N > template < class T, int N >
struct check_sizeof : _n_is_equal< sizeof(T), N > {}; struct check_sizeof : _n_is_equal< sizeof(T), N > {};
#ifdef __MWERKS__
#ifndef offsetof #ifndef offsetof
typedef unsigned long size_t; typedef unsigned long size_t;
#define offsetof(type, member) ((size_t) & (((type*)0)->member)) #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 NESTED_CHECK_SIZEOF(parent, cls, size) extern int cls##_check[check_sizeof< parent::cls, size >::value];
#define CHECK_OFFSETOF(cls, member, offset) \ #define CHECK_OFFSETOF(cls, member, offset) \
extern int cls##_check_offset##[_n_is_equal< offsetof(cls, member), offset >::value]; 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 #else
#define CHECK_SIZEOF(cls, size) #define CHECK_SIZEOF(cls, size)
#define NESTED_CHECK_SIZEOF(parent, cls, size) #define NESTED_CHECK_SIZEOF(parent, cls, size)

View File

@ -164,6 +164,7 @@ static inline int __fpclassifyd(double x) {
#define isinf(x) (fpclassify(x) == FP_INFINITE) #define isinf(x) (fpclassify(x) == FP_INFINITE)
#define isfinite(x) ((fpclassify(x) > FP_INFINITE)) #define isfinite(x) ((fpclassify(x) > FP_INFINITE))
#ifdef __MWERKS__
extern inline float sqrtf(float x) { extern inline float sqrtf(float x) {
static const double _half = .5; static const double _half = .5;
static const double _three = 3.0; static const double _three = 3.0;
@ -198,6 +199,10 @@ _MATH_INLINE double sqrt(double x) {
} }
return INFINITY; 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 float ldexpf(float x, int exp) { return (float)ldexp((double)x, exp); }
static inline double scalbn(double x, int n) { return ldexp(x, n); } static inline double scalbn(double x, int n) { return ldexp(x, n); }

View File

@ -23,7 +23,7 @@ bool CARAMManager::Initialize(uint chunkSize) {
mpARAMStart = ARAlloc(chunkSize * numChunks); mpARAMStart = ARAlloc(chunkSize * numChunks);
mpBookKeepingMemory = (uint*)CMemory::Alloc(numChunks * 4, IAllocator::kHI_None, mpBookKeepingMemory = (uint*)CMemory::Alloc(numChunks * 4, IAllocator::kHI_None,
IAllocator::kSC_Unk1, IAllocator::kTP_Heap, IAllocator::kSC_Unk1, IAllocator::kTP_Heap,
CCallStack(-1, "??(??)")); CCallStack(-1, "\?\?(\?\?)"));
CMemory::OffsetFakeStatics(mNumChunks * 4); CMemory::OffsetFakeStatics(mNumChunks * 4);
for (uint i = 0; i < numChunks; ++i) { for (uint i = 0; i < numChunks; ++i) {

View File

@ -48,11 +48,11 @@ CArchitectureMessage MakeMsg::CreateControllerStatus(EArchMsgTarget target, cons
} }
CArchitectureMessage MakeMsg::CreateQuitGameplay(EArchMsgTarget target) { 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) { 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) { 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) { 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));
} }

View File

@ -772,10 +772,11 @@ void CPlayer::Update(float dt, CStateManager& mgr) {
switch (x2f8_morphBallState) { switch (x2f8_morphBallState) {
case kMS_Unmorphed: case kMS_Unmorphed:
case kMS_Morphing: case kMS_Morphing:
case kMS_Unmorphing: case kMS_Unmorphing: {
CTransform4f gunXf = GetModelData()->GetScaledLocatorTransform(rstl::string_l(kGunLocator)); CTransform4f gunXf = GetModelData()->GetScaledLocatorTransform(rstl::string_l(kGunLocator));
x7f4_gunWorldXf = GetTransform() * gunXf; x7f4_gunWorldXf = GetTransform() * gunXf;
break; break;
}
case kMS_Morphed: case kMS_Morphed:
break; break;
} }
@ -2005,7 +2006,8 @@ void CPlayer::UpdateFreeLook(float dt) {
} }
angleVelP = x3e8_horizFreeLookAngleVel - x3e4_freeLookYawAngle; 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) { if (0.f <= angleVelP) {
x3e4_freeLookYawAngle += dx; x3e4_freeLookYawAngle += dx;
} else { } else {

View File

@ -87,7 +87,7 @@ const char* s1 = "MiscSamus_AGSC";
const char* s2 = "UI_AGSC"; const char* s2 = "UI_AGSC";
const char* s3 = "Weapons_AGSC"; const char* s3 = "Weapons_AGSC";
const char* s4 = "ZZZ_AGSC"; const char* s4 = "ZZZ_AGSC";
const char* s5 = "??(??)"; const char* s5 = "\?\?(\?\?)";
const char* s6 = ""; const char* s6 = "";
const char* s7 = "%d"; const char* s7 = "%d";
const char* st8 = ".pak"; const char* st8 = ".pak";

View File

@ -55,6 +55,7 @@ def dtk_url(tag: str) -> str:
repo = "https://github.com/encounter/decomp-toolkit" repo = "https://github.com/encounter/decomp-toolkit"
return f"{repo}/releases/download/{tag}/dtk-{system}-{arch}{suffix}" return f"{repo}/releases/download/{tag}/dtk-{system}-{arch}{suffix}"
def objdiff_cli_url(tag: str) -> str: def objdiff_cli_url(tag: str) -> str:
uname = platform.uname() uname = platform.uname()
suffix = "" suffix = ""

View File

@ -17,7 +17,7 @@ import os
import platform import platform
import sys import sys
from pathlib import Path 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 . import ninja_syntax
from .ninja_syntax import serialize_path from .ninja_syntax import serialize_path
@ -155,6 +155,9 @@ class ProjectConfig:
self.custom_build_steps: Optional[Dict[str, List[Dict[str, Any]]]] = ( 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"] 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 # Progress output, progress.json and report.json config
self.progress = True # Enable progress output self.progress = True # Enable progress output
@ -200,9 +203,40 @@ class ProjectConfig:
out[obj.name] = obj.resolve(self, lib) out[obj.name] = obj.resolve(self, lib)
return out return out
# Gets the output path for build-related files.
def out_path(self) -> Path: def out_path(self) -> Path:
return self.build_dir / str(self.version) 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: def is_windows() -> bool:
return os.name == "nt" return os.name == "nt"
@ -214,13 +248,26 @@ CHAIN = "cmd /c " if is_windows() else ""
EXE = ".exe" 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: if flags is None:
return "" return ""
elif isinstance(flags, list): return " ".join(flags)
return " ".join(flags)
else:
return flags
# Load decomp-toolkit generated config.json # Load decomp-toolkit generated config.json
@ -253,13 +300,14 @@ def load_build_config(
return 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: def generate_build(config: ProjectConfig) -> None:
config.validate() config.validate()
objects = config.objects() objects = config.objects()
build_config = load_build_config(config, config.out_path() / "config.json") build_config = load_build_config(config, config.out_path() / "config.json")
generate_build_ninja(config, objects, build_config) generate_build_ninja(config, objects, build_config)
generate_objdiff_config(config, objects, build_config) generate_objdiff_config(config, objects, build_config)
generate_compile_commands(config, objects, build_config)
# Generate build.ninja # Generate build.ninja
@ -406,16 +454,10 @@ def generate_build_ninja(
else: else:
sys.exit("ProjectConfig.sjiswrap_tag missing") sys.exit("ProjectConfig.sjiswrap_tag missing")
wrapper = config.compiler_wrapper()
# Only add an implicit dependency on wibo if we download it # Only add an implicit dependency on wibo if we download it
wrapper = config.wrapper
wrapper_implicit: Optional[Path] = None wrapper_implicit: Optional[Path] = None
if ( if wrapper is not None and config.use_wibo():
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"
wrapper_implicit = wrapper wrapper_implicit = wrapper
n.build( n.build(
outputs=wrapper, outputs=wrapper,
@ -426,15 +468,11 @@ def generate_build_ninja(
"tag": config.wibo_tag, "tag": config.wibo_tag,
}, },
) )
if not is_windows() and wrapper is None:
wrapper = Path("wine")
wrapper_cmd = f"{wrapper} " if wrapper else "" wrapper_cmd = f"{wrapper} " if wrapper else ""
compilers = config.compilers()
compilers_implicit: Optional[Path] = None compilers_implicit: Optional[Path] = None
if config.compilers_path: if config.compilers_path is None and config.compilers_tag is not None:
compilers = config.compilers_path
elif config.compilers_tag:
compilers = config.build_dir / "compilers"
compilers_implicit = compilers compilers_implicit = compilers
n.build( n.build(
outputs=compilers, outputs=compilers,
@ -445,8 +483,6 @@ def generate_build_ninja(
"tag": config.compilers_tag, "tag": config.compilers_tag,
}, },
) )
else:
sys.exit("ProjectConfig.compilers_tag missing")
binutils_implicit = None binutils_implicit = None
if config.binutils_path: if config.binutils_path:
@ -660,7 +696,6 @@ def generate_build_ninja(
n.comment(f"Link {self.name}") n.comment(f"Link {self.name}")
if self.module_id == 0: if self.module_id == 0:
elf_path = build_path / f"{self.name}.elf" 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)}" elf_ldflags = f"$ldflags -lcf {serialize_path(self.ldscript)}"
if config.generate_map: if config.generate_map:
elf_map = map_path(elf_path) elf_map = map_path(elf_path)
@ -725,17 +760,36 @@ def generate_build_ninja(
source_added: Set[Path] = set() source_added: Set[Path] = set()
def c_build(obj: Object, src_path: Path) -> Optional[Path]: 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 # Avoid creating duplicate build rules
if obj.src_obj_path is None or obj.src_obj_path in source_added: if obj.src_obj_path is None or obj.src_obj_path in source_added:
return obj.src_obj_path return obj.src_obj_path
source_added.add(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 # Add MWCC build rule
lib_name = obj.options["lib"] lib_name = obj.options["lib"]
n.comment(f"{obj.name}: {lib_name} (linked {obj.completed})") 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: if obj.options["host"] and obj.host_obj_path is not None:
n.build( n.build(
outputs=obj.host_obj_path, 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, inputs=src_path,
variables={ variables={
"basedir": os.path.dirname(obj.host_obj_path), "basedir": os.path.dirname(obj.host_obj_path),
@ -827,10 +881,10 @@ def generate_build_ninja(
link_built_obj = obj.completed link_built_obj = obj.completed
built_obj_path: Optional[Path] = None built_obj_path: Optional[Path] = None
if obj.src_path is not None and obj.src_path.exists(): 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 # Add MWCC & host build rules
built_obj_path = c_build(obj, obj.src_path) 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 # Add assembler build rule
built_obj_path = asm_build(obj, obj.src_path, obj.src_obj_path) built_obj_path = asm_build(obj, obj.src_path, obj.src_obj_path)
else: else:
@ -1274,30 +1328,20 @@ def generate_objdiff_config(
cflags = obj.options["cflags"] cflags = obj.options["cflags"]
reverse_fn_order = False reverse_fn_order = False
if type(cflags) is list: for flag in cflags:
for flag in cflags: if not flag.startswith("-inline "):
if not flag.startswith("-inline "): continue
continue for value in flag.split(" ")[1].split(","):
for value in flag.split(" ")[1].split(","): if value == "deferred":
if value == "deferred": reverse_fn_order = True
reverse_fn_order = True elif value == "nodeferred":
elif value == "nodeferred": reverse_fn_order = False
reverse_fn_order = False
# Filter out include directories # Filter out include directories
def keep_flag(flag): def keep_flag(flag):
return not flag.startswith("-i ") and not flag.startswith("-I ") return not flag.startswith("-i ") and not flag.startswith("-I ")
cflags = list(filter(keep_flag, cflags)) 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")
compiler_version = COMPILER_MAP.get(obj.options["mw_version"]) compiler_version = COMPILER_MAP.get(obj.options["mw_version"])
if compiler_version is None: if compiler_version is None:
@ -1388,6 +1432,199 @@ def generate_objdiff_config(
json.dump(cleandict(objdiff_config), w, indent=2, default=unix_path) 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 # Calculate, print and write progress to progress.json
def calculate_progress(config: ProjectConfig) -> None: def calculate_progress(config: ProjectConfig) -> None:
config.validate() config.validate()