Update dtk-template, switch from intellisense to clangd

This commit is contained in:
LagoLunatic 2024-10-17 16:12:07 -04:00
parent c6bd52facd
commit 26ca0cd4b5
17 changed files with 1761 additions and 1435 deletions

View File

@ -12,36 +12,57 @@ permissions:
jobs:
build:
runs-on: ubuntu-latest
container: ghcr.io/zeldaret/tww-build:main
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
version: [GZLE01, GZLP01, GZLJ01]
steps:
# Checkout the repository
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: recursive
# Set Git config
- name: Git config
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
# Copy the original files to the workspace
- name: Prepare
run: cp -R /orig/${{matrix.version}} orig
run: cp -R /orig .
# Build the project
- name: Build
run: |
python configure.py --version ${{matrix.version}} --compilers /compilers --warn error --no-progress
ninja
python configure.py --version ${{matrix.version}} --compilers /compilers --warn off
ninja all_source build/${{ matrix.version }}/progress.json build/${{ matrix.version }}/report.json
python configure.py --map --version ${{ matrix.version }} \
--binutils /binutils --compilers /compilers
ninja all_source build/${{ matrix.version }}/progress.json \
build/${{ matrix.version }}/report.json
# Upload progress if we're on the main branch
- name: Upload progress
if: github.ref == 'refs/heads/main'
continue-on-error: true
env:
PROGRESS_API_KEY: ${{secrets.PROGRESS_API_KEY}}
PROGRESS_SLUG: tww
PROGRESS_API_KEY: ${{ secrets.PROGRESS_API_KEY }}
run: |
python tools/upload_progress.py -b https://progress.decomp.club/ -p tww -v ${{matrix.version}} \
build/${{matrix.version}}/progress.json
python tools/upload_progress.py -b https://progress.decomp.club/ \
-p $PROGRESS_SLUG -v ${{ matrix.version }} \
build/${{ matrix.version }}/progress.json
# Upload map files
- name: Upload map
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.version }}_maps
path: build/${{ matrix.version }}/**/*.MAP
# Upload progress report
- name: Upload report
uses: actions/upload-artifact@v4
with:
@ -52,29 +73,36 @@ jobs:
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/main'
steps:
- name: Checkout website code
uses: actions/checkout@v4
with:
repository: LagoLunatic/tww-decomp-website
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: GZLE01_report
path: ./artifacts
- name: Rename artifact
run: |
mv ./artifacts/report.json ./artifacts/progress.json
- uses: actions/setup-dotnet@v4
with:
dotnet-version: "8.x.x"
- name: Build Website
run: |
python build.py
- name: Upload website artifact
uses: actions/upload-pages-artifact@v3
with:
path: './dist'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

38
.gitignore vendored
View File

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

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

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

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

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

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

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

View File

@ -64,20 +64,20 @@ Building
```
git clone https://github.com/zeldaret/tww.git
```
- Using [Dolphin Emulator](https://dolphin-emu.org/), extract your game to `orig/GZLE01` (or `GZLJ01` for JPN, `GZLP01` for PAL).
![](assets/dolphin-extract.png)
- To save space, the only necessary files are the following. Any others can be deleted.
- `sys/main.dol`
- `files/rels/*.rel`
- `files/RELS.arc`
- Copy your game's disc image to `orig/GZLE01`.
- Supported formats: ISO (GCM), RVZ, WIA, WBFS, CISO, NFS, GCZ, TGC
- After the initial build, the disc image can be deleted to save space.
- Configure:
```
python configure.py
```
To use a version other than `GZLE01` (USA), specify `--version GZLJ01` (JPN) or `--version GZLP01` (PAL).
- Build:
```
ninja && ninja all_source
ninja
```
Diffing

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -150,8 +150,8 @@ if args.no_asm:
# Tool versions
config.binutils_tag = "2.42-1"
config.compilers_tag = "20240706"
config.dtk_tag = "v1.0.0"
config.objdiff_tag = "v2.2.1"
config.dtk_tag = "v1.1.3"
config.objdiff_tag = "v2.3.2"
config.sjiswrap_tag = "v1.1.1"
config.wibo_tag = "0.6.11"
@ -307,6 +307,12 @@ Matching = True # Object matches and should be linked
NonMatching = False # Object does not match and should not be linked
Equivalent = config.non_matching # Object should be linked when configured with --non-matching
# Object is only matching for specific versions
def MatchingFor(*versions):
return config.version in versions
config.warn_missing_config = True
config.warn_missing_source = False
config.libs = [
@ -794,10 +800,10 @@ config.libs = [
Object(NonMatching, "JSystem/JAudio/JASChannelMgr.cpp"),
Object(Matching, "JSystem/JAudio/JASOscillator.cpp"),
Object(Matching, "JSystem/JAudio/JASDriverTables.cpp"),
Object(Matching, "JSystem/JAudio/dspproc.c", extra_cflags="-lang c++ -O4 -func_align 32"),
Object(Matching, "JSystem/JAudio/dsptask.c", extra_cflags="-lang c++ -O4 -func_align 32"),
Object(Matching, "JSystem/JAudio/osdsp.c", extra_cflags="-lang c++ -O4 -func_align 32 -str nopool"),
Object(Matching, "JSystem/JAudio/osdsp_task.c", extra_cflags="-lang c++ -O4 -func_align 32"),
Object(Matching, "JSystem/JAudio/dspproc.c", extra_cflags=["-lang c++", "-O4", "-func_align 32"]),
Object(Matching, "JSystem/JAudio/dsptask.c", extra_cflags=["-lang c++", "-O4", "-func_align 32"]),
Object(Matching, "JSystem/JAudio/osdsp.c", extra_cflags=["-lang c++", "-O4", "-func_align 32", "-str nopool"]),
Object(Matching, "JSystem/JAudio/osdsp_task.c", extra_cflags=["-lang c++", "-O4", "-func_align 32"]),
Object(NonMatching, "JSystem/JAudio/JAIAnimation.cpp"),
Object(NonMatching, "JSystem/JAudio/JAIBasic.cpp"),
Object(Matching, "JSystem/JAudio/JAIBankWave.cpp"),

View File

@ -1,68 +0,0 @@
Visual Studio Code
==================
Settings
--------
Recommended `.vscode/settings.json`:
```jsonc
{
"[c]": {
"files.encoding": "utf8",
"editor.defaultFormatter": "xaver.clang-format"
},
"[cpp]": {
"files.encoding": "utf8",
"editor.defaultFormatter": "xaver.clang-format"
},
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter"
},
"files.insertFinalNewline": true,
"files.trimFinalNewlines": true,
"files.associations": {
"*.inc": "cpp",
},
"search.useIgnoreFiles": false,
"search.exclude": {
"build/*/config.json": true,
"build/**/*.MAP": true,
"build.ninja": true,
".ninja_*": true,
"objdiff.json": true,
}
}
```
C/C++ configuration
-------------------
Recommended `.vscode/c_cpp_properties.json`:
```jsonc
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/include/**",
"${workspaceFolder}/src/**"
],
"cStandard": "c99",
"cppStandard": "c++98",
"intelliSenseMode": "linux-clang-x86",
"compilerPath": "",
"configurationProvider": "ms-vscode.makefile-tools",
"browse": {
"path": [
"${workspaceFolder}/include",
"${workspaceFolder}/src"
],
"limitSymbolsToIncludedHeaders": true
}
}
],
"version": 4
}
```

View File

@ -230,15 +230,15 @@ struct TEnumerator2 {
};
template <typename T, int I>
struct TContainerEnumerator : public TEnumerator2<TLinkList<T, I>::iterator, T> {
struct TContainerEnumerator : public TEnumerator2<typename TLinkList<T, I>::iterator, T> {
inline TContainerEnumerator(TLinkList<T, I>* param_0)
: TEnumerator2<TLinkList<T, I>::iterator, T>(param_0->begin(), param_0->end()) {}
: TEnumerator2<typename TLinkList<T, I>::iterator, T>(param_0->begin(), param_0->end()) {}
};
template <typename T, int I>
struct TContainerEnumerator_const : public TEnumerator2<TLinkList<T, I>::const_iterator, const T> {
struct TContainerEnumerator_const : public TEnumerator2<typename TLinkList<T, I>::const_iterator, const T> {
inline TContainerEnumerator_const(const TLinkList<T, I>* param_0)
: TEnumerator2<TLinkList<T, I>::const_iterator, const T>(param_0->begin(), param_0->end()) {}
: TEnumerator2<typename TLinkList<T, I>::const_iterator, const T>(param_0->begin(), param_0->end()) {}
};
}; // namespace JGadget

View File

@ -88,6 +88,15 @@ public:
inline virtual void draw(T);
}; // Size: 0x04
template<>
void JPACallBackBase<JPABaseEmitter*>::init(JPABaseEmitter*) {}
template<>
void JPACallBackBase<JPABaseEmitter*>::execute(JPABaseEmitter*) {}
template<>
void JPACallBackBase<JPABaseEmitter*>::executeAfter(JPABaseEmitter*) {}
template<>
void JPACallBackBase<JPABaseEmitter*>::draw(JPABaseEmitter*) {}
template<typename T, typename U>
class JPACallBackBase2 {
public:
@ -99,6 +108,13 @@ public:
inline virtual void draw(T, U);
};
template<>
void JPACallBackBase2<JPABaseEmitter*, JPABaseParticle*>::init(JPABaseEmitter*, JPABaseParticle*) {}
template<>
void JPACallBackBase2<JPABaseEmitter*, JPABaseParticle*>::execute(JPABaseEmitter*, JPABaseParticle*) {}
template<>
void JPACallBackBase2<JPABaseEmitter*, JPABaseParticle*>::draw(JPABaseEmitter*, JPABaseParticle*) {}
struct JPAEmitterInfo {
public:
JPAEmitterInfo() : mRandom(0) {}
@ -393,13 +409,4 @@ public:
/* 0x216 */ u16 field_0x216;
};
void JPACallBackBase<JPABaseEmitter*>::init(JPABaseEmitter*) {}
void JPACallBackBase<JPABaseEmitter*>::execute(JPABaseEmitter*) {}
void JPACallBackBase<JPABaseEmitter*>::executeAfter(JPABaseEmitter*) {}
void JPACallBackBase<JPABaseEmitter*>::draw(JPABaseEmitter*) {}
void JPACallBackBase2<JPABaseEmitter*, JPABaseParticle*>::init(JPABaseEmitter*, JPABaseParticle*) {}
void JPACallBackBase2<JPABaseEmitter*, JPABaseParticle*>::execute(JPABaseEmitter*, JPABaseParticle*) {}
void JPACallBackBase2<JPABaseEmitter*, JPABaseParticle*>::draw(JPABaseEmitter*, JPABaseParticle*) {}
#endif /* JPAEMITTER_H */

View File

@ -26,7 +26,7 @@
#define offsetof(type, member) ((size_t) & (((type*)0)->member))
#endif
#ifndef __INTELLISENSE__
#ifdef __MWERKS__
#define GLUE(a, b) a##b
#define GLUE2(a, b) GLUE(a, b)
#define STATIC_ASSERT(cond) typedef char GLUE2(static_assertion_failed, __LINE__)[(cond) ? 1 : -1]

View File

@ -23,4 +23,4 @@ typedef long ptrdiff_t;
};
#endif
#endif
#endif

View File

@ -56,17 +56,7 @@ def dtk_url(tag: str) -> str:
return f"{repo}/releases/download/{tag}/dtk-{system}-{arch}{suffix}"
def sjiswrap_url(tag: str) -> str:
repo = "https://github.com/encounter/sjiswrap"
return f"{repo}/releases/download/{tag}/sjiswrap-windows-x86.exe"
def wibo_url(tag: str) -> str:
repo = "https://github.com/decompals/wibo"
return f"{repo}/releases/download/{tag}/wibo"
def objdiffcli_url(tag: str) -> str:
def objdiff_cli_url(tag: str) -> str:
uname = platform.uname()
suffix = ""
system = uname.system.lower()
@ -82,13 +72,23 @@ def objdiffcli_url(tag: str) -> str:
return f"{repo}/releases/download/{tag}/objdiff-cli-{system}-{arch}{suffix}"
def sjiswrap_url(tag: str) -> str:
repo = "https://github.com/encounter/sjiswrap"
return f"{repo}/releases/download/{tag}/sjiswrap-windows-x86.exe"
def wibo_url(tag: str) -> str:
repo = "https://github.com/decompals/wibo"
return f"{repo}/releases/download/{tag}/wibo"
TOOLS: Dict[str, Callable[[str], str]] = {
"binutils": binutils_url,
"compilers": compilers_url,
"dtk": dtk_url,
"objdiff-cli": objdiff_cli_url,
"sjiswrap": sjiswrap_url,
"wibo": wibo_url,
"objdiff-cli": objdiffcli_url,
}

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, Union, cast
from . import ninja_syntax
from .ninja_syntax import serialize_path
@ -41,8 +41,9 @@ class Object:
"asflags": None,
"asm_dir": None,
"cflags": None,
"extra_asflags": None,
"extra_cflags": None,
"extra_asflags": [],
"extra_cflags": [],
"extra_clang_flags": [],
"host": None,
"lib": None,
"mw_version": None,
@ -81,6 +82,20 @@ class Object:
set_default("shift_jis", config.shift_jis)
set_default("src_dir", config.src_dir)
# Validate progress categories
def check_category(category: str):
if not any(category == c.id for c in config.progress_categories):
sys.exit(
f"Progress category '{category}' missing from config.progress_categories"
)
progress_category = obj.options["progress_category"]
if isinstance(progress_category, list):
for category in progress_category:
check_category(category)
elif progress_category is not None:
check_category(progress_category)
# Resolve paths
build_dir = config.out_path()
obj.src_path = Path(obj.options["src_dir"]) / obj.options["source"]
@ -155,15 +170,22 @@ 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
)
self.extra_clang_flags: List[str] = [] # Extra flags for clangd
# Progress output, progress.json and report.json config
self.progress = True # Enable progress output
self.progress = True # Enable report.json generation and CLI progress output
self.progress_all: bool = True # Include combined "all" category
self.progress_modules: bool = True # Include combined "modules" category
self.progress_each_module: bool = (
False # Include individual modules, disable for large numbers of modules
)
self.progress_categories: List[ProgressCategory] = [] # Additional categories
self.print_progress_categories: Union[bool, List[str]] = (
True # Print additional progress categories in the CLI progress output
)
# Progress fancy printing
self.progress_use_fancy: bool = False
@ -200,9 +222,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 +267,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 +319,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 +473,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 +487,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 +502,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 +715,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 +779,33 @@ 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 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
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 len(extra_cflags) > 0:
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 +837,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),
@ -789,7 +859,7 @@ def generate_build_ninja(
if obj.options["asflags"] is None:
sys.exit("ProjectConfig.asflags missing")
asflags_str = make_flags_str(obj.options["asflags"])
if obj.options["extra_asflags"] is not None:
if len(obj.options["extra_asflags"]) > 0:
extra_asflags_str = make_flags_str(obj.options["extra_asflags"])
asflags_str += " " + extra_asflags_str
@ -827,10 +897,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,37 +1344,27 @@ 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:
print(f"Missing scratch compiler mapping for {obj.options['mw_version']}")
else:
cflags_str = make_flags_str(cflags)
if obj.options["extra_cflags"] is not None:
if len(obj.options["extra_cflags"]) > 0:
extra_cflags_str = make_flags_str(obj.options["extra_cflags"])
cflags_str += " " + extra_cflags_str
unit_config["scratch"] = {
@ -1388,6 +1448,219 @@ 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, ...] = (
# Recursive includes are not supported by modern compilers
"-ir ",
)
# 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=c99"),
"c99": ("--language=c", "--std=c99"),
"c++": ("--language=c++", "--std=c++98"),
"cplus": ("--language=c++", "--std=c++98"),
},
),
# Enum size
(
"-enum",
{
"min": ("-fshort-enums",),
"int": ("-fno-short-enums",),
},
),
# Common BSS
(
"-common",
{
"off": ("-fno-common",),
"on": ("-fcommon",),
},
),
)
# 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"])
append_cflags(obj.options["extra_cflags"])
cflags.extend(config.extra_clang_flags)
cflags.extend(obj.options["extra_clang_flags"])
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()
@ -1450,7 +1723,11 @@ def calculate_progress(config: ProjectConfig) -> None:
print_category("All", report_data["measures"])
for category in report_data["categories"]:
print_category(category["name"], category["measures"])
if config.print_progress_categories is True or (
isinstance(config.print_progress_categories, list)
and category["id"] in config.print_progress_categories
):
print_category(category["name"], category["measures"])
if config.progress_use_fancy:
measures = report_data["measures"]