diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index 1e17b875..e979bf0a 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -9,10 +9,25 @@ on:
paths-ignore:
- '.gitignore'
- 'README.md'
+
env:
REGISTRY: ghcr.io
jobs:
+ run-pre-commit:
+ name: Run pre-commit
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-python@v3
+ - name: Install pre-commit
+ run: |
+ pip install pre-commit
+ pre-commit install
+ - name: Run pre-commit
+ run: |
+ pre-commit run --show-diff-on-failure --color=always --all-files
+
build-container:
name: Create build container image
runs-on: ubuntu-latest
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 00000000..2582ac15
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,16 @@
+repos:
+ - repo: local
+ hooks:
+ - id: normalize-cproject
+ name: Normalize Eclipse .cproject files
+ entry: python tools/normalize_cproject.py
+ language: system
+ files: \.cproject$
+ pass_filenames: true
+
+ - repo: https://github.com/astral-sh/ruff-pre-commit
+ rev: v0.4.3
+ hooks:
+ - id: ruff
+ args: [--fix]
+ - id: ruff-format
diff --git a/misc/firmware-eraser/.cproject b/misc/firmware-eraser/.cproject
index 38bd6a04..e43de06c 100644
--- a/misc/firmware-eraser/.cproject
+++ b/misc/firmware-eraser/.cproject
@@ -23,7 +23,7 @@
-
+
@@ -264,7 +264,7 @@
-
+
@@ -330,7 +330,7 @@
-
+
diff --git a/src/ot-rcp/.cproject b/src/ot-rcp/.cproject
index 7d9646a7..e4436990 100644
--- a/src/ot-rcp/.cproject
+++ b/src/ot-rcp/.cproject
@@ -23,7 +23,7 @@
-
+
@@ -442,7 +442,7 @@
-
+
diff --git a/src/rcp-uart-802154/.cproject b/src/rcp-uart-802154/.cproject
index cecebab1..c8e71939 100644
--- a/src/rcp-uart-802154/.cproject
+++ b/src/rcp-uart-802154/.cproject
@@ -23,7 +23,7 @@
-
+
@@ -499,7 +499,7 @@
-
+
diff --git a/tools/build_project.py b/tools/build_project.py
index d677cb27..7852ead4 100755
--- a/tools/build_project.py
+++ b/tools/build_project.py
@@ -9,7 +9,6 @@
import sys
import copy
import json
-import shlex
import shutil
import hashlib
import logging
@@ -489,7 +488,7 @@ def main():
f' --parameter sdk_dir:"{sdk}"'
"\n"
)
- f.write(f"\t-@echo ' '")
+ f.write("\t-@echo ' '")
subprocess.run(
[
@@ -534,7 +533,7 @@ def main():
# Insert our postbuild step
cmakelists_txt = cmake_build_root / "CMakeLists.txt"
cmakelists = cmakelists_txt.read_text()
- s37_line = next(l for l in cmakelists.split("\n") if "-O srec" in l)
+ s37_line = next(line for line in cmakelists.split("\n") if "-O srec" in line)
s37_output_file = s37_line.split(" ")[-1]
s37_build_folder = s37_output_file.split("/", 1)[0] + '"'
diff --git a/tools/normalize_cproject.py b/tools/normalize_cproject.py
new file mode 100644
index 00000000..4d4e7116
--- /dev/null
+++ b/tools/normalize_cproject.py
@@ -0,0 +1,54 @@
+import re
+import sys
+import json
+import pathlib
+import xml.etree.ElementTree as ET
+
+
+def json_dumps_compact(obj: dict | list) -> str:
+ """Compactly dump JSON into a string."""
+ return json.dumps(obj, separators=(",", ":"), indent=None)
+
+
+cproject_path = pathlib.Path(sys.argv[1])
+cproject = cproject_path.read_text()
+
+# Capture all preprocessing directives verbatim
+match = re.search(r"(<\?.*\?>)", cproject[:200], flags=re.DOTALL)
+processing_instructions = match.group(0)
+
+tree = ET.fromstring(cproject)
+
+for storage_module in tree.findall(".//storageModule"):
+ if "projectCommon.copiedFiles" in storage_module.attrib:
+ copied_files = json.loads(storage_module.attrib["projectCommon.copiedFiles"])
+ copied_files.sort(
+ key=lambda f: (f["generated"], f["projectPath"], f["version"])
+ )
+ storage_module.attrib["projectCommon.copiedFiles"] = json_dumps_compact(
+ copied_files
+ )
+
+ if "cppBuildConfig.projectBuiltInState" in storage_module.attrib:
+ project_built_in_state = json.loads(
+ storage_module.attrib["cppBuildConfig.projectBuiltInState"]
+ )
+
+ for state in project_built_in_state:
+ if "resolvedOptionsStr" in state:
+ resolved_options = json.loads(state["resolvedOptionsStr"])
+ resolved_options.sort(key=lambda o: o["optionId"])
+
+ state["resolvedOptionsStr"] = json_dumps_compact(resolved_options)
+
+ storage_module.attrib["cppBuildConfig.projectBuiltInState"] = (
+ json_dumps_compact(project_built_in_state)
+ )
+
+# Normalize self-closing tag spacing
+xml_text = ET.tostring(tree, encoding="unicode", xml_declaration=False)
+xml_text = xml_text.replace(" />", "/>")
+
+# Only touch the filesystem if we need to
+if processing_instructions + xml_text != cproject:
+ cproject_path.write_text(processing_instructions + xml_text)