From c446ab18611ff59966beb40c8beefae420547e45 Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Mon, 6 May 2024 16:59:47 -0400 Subject: [PATCH] Normalize `.cproject` files with pre-commit (#60) * Create a pre-commit hook to normalize .cproject files * Normalize existing projects * Normalize deeper * Clean up normalizer script * Add ruff * Run ruff * Run pre-commit in CI --- .github/workflows/build.yaml | 15 ++++++++++ .pre-commit-config.yaml | 16 ++++++++++ misc/firmware-eraser/.cproject | 6 ++-- src/ot-rcp/.cproject | 4 +-- src/rcp-uart-802154/.cproject | 4 +-- tools/build_project.py | 5 ++-- tools/normalize_cproject.py | 54 ++++++++++++++++++++++++++++++++++ 7 files changed, 94 insertions(+), 10 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100644 tools/normalize_cproject.py diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 692f83c3..3afe3464 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 bf2b68e1..9f377645 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 @@ -511,7 +510,7 @@ def main(): f' --parameter sdk_dir:"{sdk}"' "\n" ) - f.write(f"\t-@echo ' '") + f.write("\t-@echo ' '") subprocess.run( [ @@ -556,7 +555,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)