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__/
*.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

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": [
"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",
]
}

18
.vscode/settings.json vendored
View File

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

32
.vscode/tasks.json vendored
View File

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

View File

@ -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);

View File

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

View File

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

View File

@ -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__)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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) {

View File

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

View File

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

View File

@ -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";

View File

@ -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 = ""

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-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()