From 940fead9059645f90631ade4934c32bae1a3d785 Mon Sep 17 00:00:00 2001 From: khodowany Date: Thu, 10 Sep 2026 18:53:56 -0500 Subject: [PATCH] Editor --- .clang-format | 1 - .clangd | 9 ----- .gitea/workflows/ci.yml | 45 +--------------------- .gitignore | 34 ++++++++--------- README.md | 25 +++++++++++- conanfile.py | 1 - modulus.json | 9 +++++ modulus/cli.py | 10 +++++ modulus/config.py | 42 ++++++++++++++++++++ modulus/export.py | 31 ++++++++------- pyrightconfig.json | 14 ------- schemas/modulus.schema.json | 13 +++++++ tests/test_cli.py | 48 ++++++++++++++++++++++- tests/test_export.py | 68 ++++++++++++++++++++++----------- tools/editor/clang-format | 1 - tools/editor/clangd.yaml | 9 ----- tools/editor/pyrightconfig.json | 14 ------- 17 files changed, 225 insertions(+), 149 deletions(-) delete mode 100644 .clang-format delete mode 100644 .clangd delete mode 100644 pyrightconfig.json delete mode 100644 tools/editor/clang-format delete mode 100644 tools/editor/clangd.yaml delete mode 100644 tools/editor/pyrightconfig.json diff --git a/.clang-format b/.clang-format deleted file mode 100644 index e384528..0000000 --- a/.clang-format +++ /dev/null @@ -1 +0,0 @@ -DisableFormat: true diff --git a/.clangd b/.clangd deleted file mode 100644 index 7703ad6..0000000 --- a/.clangd +++ /dev/null @@ -1,9 +0,0 @@ -CompileFlags: - CompilationDatabase: build/build_x86_debug - Add: - - -Wall - - -Wextra - -Diagnostics: - ClangTidy: - Remove: ['*'] diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 83b6978..717dd3d 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -23,39 +23,6 @@ jobs: - uses: actions/checkout@v4 - name: Install build dependencies run: | - if [ -r /etc/os-release ]; then - . /etc/os-release - fi - if [ "${ID:-}" = "ubuntu" ] && [ -n "${VERSION_CODENAME:-}" ]; then - if [ -f /etc/apt/sources.list.d/ubuntu.sources ]; then - sed -i '/^Types: deb$/a Architectures: amd64' /etc/apt/sources.list.d/ubuntu.sources - printf '%s\n' \ - 'Types: deb' \ - 'URIs: http://ports.ubuntu.com/ubuntu-ports/' \ - "Suites: ${VERSION_CODENAME} ${VERSION_CODENAME}-updates ${VERSION_CODENAME}-backports" \ - 'Components: main universe restricted multiverse' \ - 'Architectures: arm64' \ - 'Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg' \ - '' \ - 'Types: deb' \ - 'URIs: http://ports.ubuntu.com/ubuntu-ports/' \ - "Suites: ${VERSION_CODENAME}-security" \ - 'Components: main universe restricted multiverse' \ - 'Architectures: arm64' \ - 'Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg' \ - > /etc/apt/sources.list.d/ubuntu-arm64.sources - elif [ -f /etc/apt/sources.list ]; then - sed -i 's|^deb http://archive.ubuntu.com/ubuntu|deb [arch=amd64] http://archive.ubuntu.com/ubuntu|' /etc/apt/sources.list - sed -i 's|^deb http://security.ubuntu.com/ubuntu|deb [arch=amd64] http://security.ubuntu.com/ubuntu|' /etc/apt/sources.list - printf '%s\n' \ - "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports ${VERSION_CODENAME} main universe restricted multiverse" \ - "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports ${VERSION_CODENAME}-updates main universe restricted multiverse" \ - "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports ${VERSION_CODENAME}-backports main universe restricted multiverse" \ - "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports ${VERSION_CODENAME}-security main universe restricted multiverse" \ - > /etc/apt/sources.list.d/ubuntu-arm64.list - fi - fi - dpkg --add-architecture arm64 apt-get update apt-get install -y --no-install-recommends \ build-essential \ @@ -66,16 +33,6 @@ jobs: gcc-12 \ gcc-aarch64-linux-gnu \ git \ - libdrm-dev \ - libdrm-dev:arm64 \ - libegl-dev \ - libegl-dev:arm64 \ - libgbm-dev \ - libgbm-dev:arm64 \ - libgles-dev \ - libgles-dev:arm64 \ - libglvnd-dev \ - libglvnd-dev:arm64 \ ninja-build \ pkg-config \ python3 \ @@ -91,6 +48,8 @@ jobs: restore-keys: conan-${{ runner.os }}- - name: Configure native release run: ./tools/configure.py --target x86_64_gcc12_release + - name: Test tooling + run: python3 -m unittest discover -s tests -p 'test_*.py' - name: Build native release run: ./tools/build.py --target x86_64_gcc12_release - name: Test native release diff --git a/.gitignore b/.gitignore index a81655b..bbf8906 100644 --- a/.gitignore +++ b/.gitignore @@ -1,25 +1,21 @@ -__pycache__/ +build/ +.clangd +.clang-format +pyrightconfig.json + **/__pycache__/ *.pyc *.pyo *.pyd +*.log .tmp/ -build/ -CMakeUserPresets.json -package/CMakeCache.txt -package/CMakeFiles/ -package/metadata/ -package/.ninja_* -package/*.json -package/*.cmake -package/*.ninja -package/*.sh -package/*-smoke -package/Boost* -package/boost* -package/modulus-* -package/modulusTargets.cmake -package/cmakedeps_macros.cmake -package/conan_toolchain.cmake -package/conandeps_legacy.cmake +CMakeFiles/ +CMakeCache.txt +CMakeUserPresets.json +cmake_install.cmake +CTestTestfile.cmake +Testing/ +build.ninja +.ninja_* +compile_commands.json diff --git a/README.md b/README.md index e71ea09..978f560 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Modulus -Modulus is the shared C++23 foundation library and build tooling used by the khodo Product Vamp repositories. It provides small runtime utilities, Conan profiles, CMake package integration, editor templates, typed LSP compile-database setup, and bridge targets for the external libraries needed by Product Vamp. +Modulus is the shared C++23 foundation library and build tooling used by the khodo Product Vamp repositories. It provides small runtime utilities, Conan profiles, CMake package integration, editor configuration generation, typed LSP compile-database setup, and bridge targets for the external libraries needed by Product Vamp. The core `modulus::modulus` target requires only Boost.JSON. Optional bridges are loaded when requested: @@ -35,6 +35,29 @@ Refresh clangd compile databases with: Product repos can add typed extra CMake compile databases under `tooling.lsp.compile_databases`; this is intended for cases like Product Vamp's Pico firmware tree. +## Editor Configuration + +`modulus.json` is the source for generated `.clangd`, `.clang-format`, and `pyrightconfig.json` files. Configure, LSP refresh, and tooling export regenerate them only when their content changes. Keep these generated files ignored; customize `tooling.editor` instead: + +```json +{ + "tooling": { + "editor": { + "clangd": [ + {"CompileFlags": {"CompilationDatabase": "build/native"}}, + {"If": {"PathMatch": "src/firmware/.*"}, "CompileFlags": {"CompilationDatabase": "build/firmware"}} + ], + "clang_format": {"DisableFormat": true}, + "pyright": {"include": ["tools", "conanfile.py"]} + } + } +} +``` + +Each supplied entry replaces that file's entire configuration. Omitted entries use Modulus defaults: the default target's build directory for clangd, `-Wall`/`-Wextra`, disabled clang-tidy and formatting, and Python paths for the bootstrap, recipe, and exported tools. Entries use the editors' own configuration keys. LLVM files use JSON-compatible YAML, with separate documents for clangd fragments. + +The authored `cmake/` directory belongs in Git. `CMakeFiles/`, compiler caches, and generated sources belong in build directories. Modulus rejects project/source-root output directories before clearing configure or LSP caches. Conan may still use its own isolated build folders. Existing build caches are retained; they are not source files. + ## Package Create Conan packages with: diff --git a/conanfile.py b/conanfile.py index 3b86221..21ac319 100644 --- a/conanfile.py +++ b/conanfile.py @@ -80,7 +80,6 @@ class modulus_recipe(conan.ConanFile): "tools/*.py", "tools/conan/profiles/*", "tools/docker/*", - "tools/editor/*", "README.md", ) diff --git a/modulus.json b/modulus.json index 4b56452..d8e01d2 100644 --- a/modulus.json +++ b/modulus.json @@ -8,6 +8,15 @@ ] }, "tooling": { + "editor": { + "pyright": { + "include": ["modulus", "tools", "tests", "conanfile.py"], + "extraPaths": ["."], + "typeCheckingMode": "off", + "reportMissingImports": false, + "reportMissingModuleSource": false + } + }, "remote": { "name": "khodo", "url": "https://git.thyme-labs.com/api/packages/khodo/conan" diff --git a/modulus/cli.py b/modulus/cli.py index 4e9786e..df1199b 100644 --- a/modulus/cli.py +++ b/modulus/cli.py @@ -10,6 +10,7 @@ import tarfile import urllib.parse import modulus.config +import modulus.export build_root = pathlib.Path("build") common_dir = build_root / "build_common" @@ -28,6 +29,8 @@ def main(command): repo_root = find_repo_root(pathlib.Path.cwd()) config = modulus.config.load_repo_config(repo_root) context = build_context(repo_root, config) + if command in {"configure", "lsp"}: + modulus.export.sync_editor_files(repo_root, config) if command == "configure": return configure(context, sys.argv[1:]) if command == "build": @@ -78,6 +81,7 @@ class build_context: result = {} targets = self.tooling["targets"] for name, target in targets.items(): + validate_build_directory(self.repo_root, self.repo_root, self.repo_root / str(target["build_dir"])) result[name] = build_target( name=name, profile=self.repo_root / str(target["profile"]), @@ -610,9 +614,15 @@ def clear_lsp_cmake_cache(build_dir): if cmake_files.exists(): shutil.rmtree(cmake_files) +def validate_build_directory(repo_root, source_dir, build_dir): + resolved = pathlib.Path(build_dir).resolve() + if resolved in {pathlib.Path(repo_root).resolve(), pathlib.Path(source_dir).resolve()}: + raise modulus.config.config_error(f"build directory must differ from the project and source roots: {build_dir}") + def configure_lsp_database(context, database): build_dir = context.repo_root / str(database["build_dir"]) source_dir = context.repo_root / str(database.get("source_dir", ".")) + validate_build_directory(context.repo_root, source_dir, build_dir) build_dir.mkdir(parents=True, exist_ok=True) clear_lsp_cmake_cache(build_dir) return run( diff --git a/modulus/config.py b/modulus/config.py index 81377ab..ad21def 100644 --- a/modulus/config.py +++ b/modulus/config.py @@ -77,6 +77,7 @@ test_keys = { "name", } tooling_keys = { + "editor", "default_target", "targets", "docker", @@ -255,8 +256,47 @@ def normalize(data): for target in tooling["targets"].values(): target.setdefault("cmake_definitions", {}) validate_config_references(data) + editor = tooling.setdefault("editor", {}) + for name, value in default_editor_config(tooling).items(): + editor.setdefault(name, value) return data +def default_editor_config(tooling): + target = tooling["targets"][tooling["default_target"]] + return { + "clangd": [{ + "CompileFlags": { + "CompilationDatabase": target["build_dir"], + "Add": ["-Wall", "-Wextra"], + }, + "Diagnostics": {"ClangTidy": {"Remove": ["*"]}}, + }], + "clang_format": {"DisableFormat": True}, + "pyright": { + "include": ["modulus.py", "conanfile.py", "build/build_tools"], + "extraPaths": ["build/build_common/modulus", "."], + "typeCheckingMode": "off", + "reportMissingImports": False, + "reportMissingModuleSource": False, + }, + } + +def validate_editor(editor): + if not isinstance(editor, dict): + raise config_error("modulus.json tooling.editor must be an object") + validate_keys(editor, {"clangd", "clang_format", "pyright"}, "tooling.editor") + for name, value in editor.items(): + field = f"tooling.editor.{name}" + if name == "clangd": + if not isinstance(value, list) or not value or not all(isinstance(fragment, dict) for fragment in value): + raise config_error(f"modulus.json {field} must be a non-empty list of configuration objects") + elif not isinstance(value, dict): + raise config_error(f"modulus.json {field} must be an object") + try: + json.dumps(value, allow_nan=False) + except (ValueError, TypeError) as error: + raise config_error(f"modulus.json {field} must contain valid JSON values") from error + def validate_raw_config(data): validate_keys(data, top_level_keys, "modulus.json") package = require_mapping(data, "package") @@ -340,6 +380,8 @@ def validate_tests(tests): validate_cmake_name(tests.get("name"), "tests.name", optional=True) def validate_tooling(tooling): + if "editor" in tooling: + validate_editor(tooling["editor"]) validate_cmake_name(tooling.get("default_target"), "tooling.default_target", optional=True) validate_env_name(tooling.get("upload_env"), "tooling.upload_env", optional=True) run = tooling.get("run") diff --git a/modulus/export.py b/modulus/export.py index ac7c1b7..5520d50 100644 --- a/modulus/export.py +++ b/modulus/export.py @@ -1,3 +1,4 @@ +import json import pathlib import shutil @@ -34,18 +35,18 @@ runtime_ignore_patterns = ( ) editor_files = { - "clangd.yaml": ".clangd", - "clang-format": ".clang-format", - "pyrightconfig.json": "pyrightconfig.json", + "clangd": ".clangd", + "clang_format": ".clang-format", + "pyright": "pyrightconfig.json", } def sync_project(repo_root, package_root): repo_root = pathlib.Path(repo_root).resolve() package_root = pathlib.Path(package_root).resolve() - modulus.config.load_repo_config(repo_root) + config = modulus.config.load_repo_config(repo_root) sync_runtime(repo_root, package_root) sync_tools(repo_root, package_root) - sync_editor_files(repo_root, package_root) + sync_editor_files(repo_root, config) def sync_runtime(repo_root, package_root): dst = repo_root / runtime_dir @@ -68,15 +69,17 @@ def sync_tools(repo_root, package_root): shutil.copytree(src, dst, ignore=shutil.ignore_patterns("__pycache__", "*.pyc", "*.pyo", "*.pyd")) chmod_python_tools(dst) -def sync_editor_files(repo_root, package_root): - src = package_root / "tools" / "editor" - if not src.is_dir(): - raise RuntimeError(f"Modulus package is missing editor templates: {src}") - for source_name, destination_name in editor_files.items(): - source = src / source_name - if not source.is_file(): - raise RuntimeError(f"Modulus package is missing editor template: {source}") - shutil.copy2(source, repo_root / destination_name) +def sync_editor_files(repo_root, config): + editor = config["tooling"]["editor"] + for name, destination in editor_files.items(): + value = editor[name] + fragments = value if name == "clangd" else [value] + content = "\n---\n".join(json.dumps(fragment, indent=2, allow_nan=False) for fragment in fragments) + "\n" + if name != "pyright": + content = "# Generated by Modulus from modulus.json tooling.editor.\n" + content + path = pathlib.Path(repo_root) / destination + if not path.exists() or path.read_text(encoding="utf-8") != content: + path.write_text(content, encoding="utf-8") def replace_directory(path): if path.exists(): diff --git a/pyrightconfig.json b/pyrightconfig.json deleted file mode 100644 index 17499c9..0000000 --- a/pyrightconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "include": [ - "modulus.py", - "package/conanfile.py", - "build/build_tools" - ], - "extraPaths": [ - "build/build_common/modulus", - "." - ], - "typeCheckingMode": "off", - "reportMissingImports": false, - "reportMissingModuleSource": false -} diff --git a/schemas/modulus.schema.json b/schemas/modulus.schema.json index 771c125..bf3b7cc 100644 --- a/schemas/modulus.schema.json +++ b/schemas/modulus.schema.json @@ -137,6 +137,19 @@ "default_target": { "$ref": "#/$defs/cmake_name" }, + "editor": { + "type": "object", + "additionalProperties": false, + "properties": { + "clangd": { + "type": "array", + "minItems": 1, + "items": { "type": "object" } + }, + "clang_format": { "type": "object" }, + "pyright": { "type": "object" } + } + }, "upload_env": { "type": "string", "pattern": "^[A-Z_][A-Z0-9_]*$" diff --git a/tests/test_cli.py b/tests/test_cli.py index 28d1754..2167c10 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,3 +1,4 @@ +import json import os import pathlib import subprocess @@ -69,6 +70,49 @@ class upload_packages_test(unittest.TestCase): completed = subprocess.run(command, cwd=repo, check=False) self.assertEqual(completed.returncode, 0) + def test_project_root_build_is_rejected(self): + with tempfile.TemporaryDirectory() as directory: + root = pathlib.Path(directory) + context = self.context(root) + context.config["tooling"]["targets"]["native"]["build_dir"] = "." + with self.assertRaisesRegex(modulus.config.config_error, "project and source roots"): + modulus.cli.build_context(root, context.config) + + def test_configure_and_lsp_refresh_project_editor_settings(self): + with tempfile.TemporaryDirectory() as directory: + root = pathlib.Path(directory) + fragments = [ + {"CompileFlags": {"CompilationDatabase": "build/native"}}, + {"If": {"PathMatch": "src/pico/.*"}, "CompileFlags": {"CompilationDatabase": "build/pico"}}, + ] + config = {"package": {"name": "sample"}, "tooling": {"editor": {"clangd": fragments}}} + (root / "modulus.json").write_text(json.dumps(config), encoding="utf-8") + with unittest.mock.patch.object(modulus.cli, "find_repo_root", return_value=root): + for command in ("configure", "lsp"): + with self.subTest(command=command), unittest.mock.patch.object(modulus.cli, command, return_value=0): + (root / ".clangd").write_text("stale", encoding="utf-8") + self.assertEqual(modulus.cli.main(command), 0) + content = (root / ".clangd").read_text(encoding="utf-8").split("\n", 1)[1] + self.assertEqual([json.loads(part) for part in content.split("\n---\n")], fragments) + before = {name: (root / name).stat().st_mtime_ns for name in modulus.export.editor_files.values()} + self.assertEqual(modulus.cli.main(command), 0) + self.assertEqual(before, {name: (root / name).stat().st_mtime_ns for name in before}) + + def test_lsp_rejects_source_root_before_cache_cleanup(self): + with tempfile.TemporaryDirectory() as directory: + root = pathlib.Path(directory) + context = self.context(root) + cache = root / "CMakeCache.txt" + cache.write_text("keep", encoding="utf-8") + with unittest.mock.patch.object(modulus.cli, "clear_lsp_cmake_cache") as clear: + for build_dir in (".", "alias", "subproject"): + if build_dir == "alias": + (root / "alias").symlink_to(root, target_is_directory=True) + with self.subTest(build_dir=build_dir), self.assertRaises(modulus.config.config_error): + modulus.cli.configure_lsp_database(context, {"source_dir": "subproject", "build_dir": build_dir}) + clear.assert_not_called() + self.assertEqual(cache.read_text(encoding="utf-8"), "keep") + @unittest.mock.patch.object(modulus.cli, "upload_all_packages", return_value=37) @unittest.mock.patch.object(modulus.cli, "configure_conan_remote", return_value=0) def test_failed_upload_does_not_force_by_default(self, configure, upload): @@ -511,7 +555,9 @@ class upload_packages_test(unittest.TestCase): @unittest.mock.patch.object(modulus.cli, "run", return_value=0) @unittest.mock.patch.object(modulus.cli, "in_docker", return_value=True) def test_build_can_target_one_cmake_target(self, docker, run): - context = self.context() + directory = tempfile.TemporaryDirectory() + self.addCleanup(directory.cleanup) + context = self.context(pathlib.Path(directory.name)) context.default_target.build_dir.mkdir(parents=True, exist_ok=True) (context.default_target.build_dir / "CMakeCache.txt").write_text("", encoding="utf-8") (context.default_target.build_dir / "build.ninja").write_text("", encoding="utf-8") diff --git a/tests/test_export.py b/tests/test_export.py index e901096..73c4eb4 100644 --- a/tests/test_export.py +++ b/tests/test_export.py @@ -31,7 +31,6 @@ class modulus_export_test(unittest.TestCase): for directory in ( "modulus", "tools/docker", - "tools/editor", "include/modulus", "tests", "lib/cmake/modulus", @@ -48,9 +47,6 @@ class modulus_export_test(unittest.TestCase): (package / "tools" / "lsp.py").write_text("#!/usr/bin/env python3\n", encoding="utf-8") (package / "tools" / "run.py").write_text("#!/usr/bin/env python3\n", encoding="utf-8") (package / "tools" / "docker" / "Dockerfile").write_text("FROM scratch\n", encoding="utf-8") - (package / "tools" / "editor" / "clangd.yaml").write_text("CompileFlags: {}\n", encoding="utf-8") - (package / "tools" / "editor" / "clang-format").write_text("DisableFormat: true\n", encoding="utf-8") - (package / "tools" / "editor" / "pyrightconfig.json").write_text("{}\n", encoding="utf-8") (package / "include" / "modulus" / "types.hpp").write_text("", encoding="utf-8") (package / "tests" / "test.cpp").write_text("", encoding="utf-8") (package / "lib" / "cmake" / "modulus" / "modulus-config.cmake").write_text("", encoding="utf-8") @@ -224,8 +220,8 @@ class modulus_export_test(unittest.TestCase): self.assertTrue((repo / "build" / "build_tools" / "docker" / "Dockerfile").is_file()) self.assertFalse((repo / "build" / "build_tools" / "docs").exists()) self.assertFalse((repo / "build" / "build_tools" / "lsp").exists()) - for source_name, destination_name in modulus.export.editor_files.items(): - self.assertEqual((repo / destination_name).read_text(encoding="utf-8"), (package / "tools" / "editor" / source_name).read_text(encoding="utf-8")) + for destination in modulus.export.editor_files.values(): + self.assertTrue((repo / destination).is_file()) runtime = repo / "build" / "build_common" / "modulus" self.assertTrue((runtime / "modulus" / "cli.py").is_file()) self.assertTrue((runtime / "modulus" / "config.py").is_file()) @@ -244,23 +240,51 @@ class modulus_export_test(unittest.TestCase): self.assertFalse((runtime / "share").exists()) self.assertFalse((runtime / "bin").exists()) - def test_root_editor_files_match_export_templates(self): - root = pathlib.Path(__file__).resolve().parents[1] - for source_name, destination_name in modulus.export.editor_files.items(): - self.assertEqual((root / destination_name).read_text(encoding="utf-8"), (root / "tools" / "editor" / source_name).read_text(encoding="utf-8")) - - def test_clangd_template_uses_default_target_compile_database_and_disables_clang_tidy(self): - root = pathlib.Path(__file__).resolve().parents[1] + def test_editor_defaults_use_configured_target(self): config = modulus.config.normalize(self.config()) - default_target = config["tooling"]["targets"][config["tooling"]["default_target"]] - template = (root / "tools" / "editor" / "clangd.yaml").read_text(encoding="utf-8") - self.assertIn(f"CompilationDatabase: {default_target['build_dir']}", template) - self.assertIn("Diagnostics:", template) - self.assertIn("ClangTidy:", template) - self.assertIn("Remove: ['*']", template) - self.assertNotIn("bugprone-*", template) - self.assertNotIn("performance-*", template) - self.assertNotIn("readability-*", template) + config["tooling"].pop("editor") + config["tooling"]["targets"][config["tooling"]["default_target"]]["build_dir"] = "build/custom" + config = modulus.config.normalize(config) + editor = config["tooling"]["editor"] + self.assertEqual(editor["clangd"][0]["CompileFlags"]["CompilationDatabase"], "build/custom") + self.assertEqual(editor["clangd"][0]["Diagnostics"]["ClangTidy"]["Remove"], ["*"]) + self.assertIn("conanfile.py", editor["pyright"]["include"]) + self.assertNotIn("package/conanfile.py", editor["pyright"]["include"]) + + def test_editor_replacements_and_refresh_are_idempotent(self): + config = self.config() + editor = { + "clangd": [ + {"CompileFlags": {"CompilationDatabase": "build/native"}}, + {"If": {"PathMatch": "src/pico/.*"}, "CompileFlags": {"CompilationDatabase": "build/pico"}}, + ], + "clang_format": {"BasedOnStyle": "LLVM", "IndentWidth": 2}, + "pyright": {"include": ["generator"]}, + } + config["tooling"] = {"editor": editor} + normalized = modulus.config.normalize(config) + self.assertEqual(normalized["tooling"]["editor"], editor) + with tempfile.TemporaryDirectory() as directory: + root = pathlib.Path(directory) + modulus.export.sync_editor_files(root, normalized) + for name, filename in modulus.export.editor_files.items(): + path = root / filename + content = path.read_text(encoding="utf-8") + if name != "pyright": + content = content.split("\n", 1)[1] + documents = [json.loads(part) for part in content.split("\n---\n")] + self.assertEqual(documents if name == "clangd" else documents[0], editor[name]) + before = {path.name: path.stat().st_mtime_ns for path in root.iterdir()} + modulus.export.sync_editor_files(root, normalized) + self.assertEqual(before, {path.name: path.stat().st_mtime_ns for path in root.iterdir()}) + + def test_editor_config_rejects_invalid_shapes(self): + for editor in (None, [], {"unknown": {}}, {"clangd": {}}, {"clangd": []}, {"clangd": ["bad"]}, {"pyright": []}, {"clang_format": None}, {"pyright": {"value": float("nan")}}): + with self.subTest(editor=editor): + config = self.config() + config["tooling"] = {"editor": editor} + with self.assertRaises(modulus.config.config_error): + modulus.config.normalize(config) def test_export_requires_packaged_tools(self): with tempfile.TemporaryDirectory() as repo_dir, tempfile.TemporaryDirectory() as package_dir: diff --git a/tools/editor/clang-format b/tools/editor/clang-format deleted file mode 100644 index e384528..0000000 --- a/tools/editor/clang-format +++ /dev/null @@ -1 +0,0 @@ -DisableFormat: true diff --git a/tools/editor/clangd.yaml b/tools/editor/clangd.yaml deleted file mode 100644 index 7703ad6..0000000 --- a/tools/editor/clangd.yaml +++ /dev/null @@ -1,9 +0,0 @@ -CompileFlags: - CompilationDatabase: build/build_x86_debug - Add: - - -Wall - - -Wextra - -Diagnostics: - ClangTidy: - Remove: ['*'] diff --git a/tools/editor/pyrightconfig.json b/tools/editor/pyrightconfig.json deleted file mode 100644 index 17499c9..0000000 --- a/tools/editor/pyrightconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "include": [ - "modulus.py", - "package/conanfile.py", - "build/build_tools" - ], - "extraPaths": [ - "build/build_common/modulus", - "." - ], - "typeCheckingMode": "off", - "reportMissingImports": false, - "reportMissingModuleSource": false -}