Editor
publish / publish (push) Successful in 11m35s

This commit is contained in:
khodowany
2026-09-10 18:53:56 -05:00
parent 48db1a287f
commit 940fead905
17 changed files with 225 additions and 149 deletions
-1
View File
@@ -1 +0,0 @@
DisableFormat: true
-9
View File
@@ -1,9 +0,0 @@
CompileFlags:
CompilationDatabase: build/build_x86_debug
Add:
- -Wall
- -Wextra
Diagnostics:
ClangTidy:
Remove: ['*']
+2 -43
View File
@@ -23,39 +23,6 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install build dependencies - name: Install build dependencies
run: | 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 update
apt-get install -y --no-install-recommends \ apt-get install -y --no-install-recommends \
build-essential \ build-essential \
@@ -66,16 +33,6 @@ jobs:
gcc-12 \ gcc-12 \
gcc-aarch64-linux-gnu \ gcc-aarch64-linux-gnu \
git \ 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 \ ninja-build \
pkg-config \ pkg-config \
python3 \ python3 \
@@ -91,6 +48,8 @@ jobs:
restore-keys: conan-${{ runner.os }}- restore-keys: conan-${{ runner.os }}-
- name: Configure native release - name: Configure native release
run: ./tools/configure.py --target x86_64_gcc12_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 - name: Build native release
run: ./tools/build.py --target x86_64_gcc12_release run: ./tools/build.py --target x86_64_gcc12_release
- name: Test native release - name: Test native release
+15 -19
View File
@@ -1,25 +1,21 @@
__pycache__/ build/
.clangd
.clang-format
pyrightconfig.json
**/__pycache__/ **/__pycache__/
*.pyc *.pyc
*.pyo *.pyo
*.pyd *.pyd
*.log
.tmp/ .tmp/
build/
CMakeUserPresets.json
package/CMakeCache.txt CMakeFiles/
package/CMakeFiles/ CMakeCache.txt
package/metadata/ CMakeUserPresets.json
package/.ninja_* cmake_install.cmake
package/*.json CTestTestfile.cmake
package/*.cmake Testing/
package/*.ninja build.ninja
package/*.sh .ninja_*
package/*-smoke compile_commands.json
package/Boost*
package/boost*
package/modulus-*
package/modulusTargets.cmake
package/cmakedeps_macros.cmake
package/conan_toolchain.cmake
package/conandeps_legacy.cmake
+24 -1
View File
@@ -1,6 +1,6 @@
# Modulus # 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: 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. 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 ## Package
Create Conan packages with: Create Conan packages with:
-1
View File
@@ -80,7 +80,6 @@ class modulus_recipe(conan.ConanFile):
"tools/*.py", "tools/*.py",
"tools/conan/profiles/*", "tools/conan/profiles/*",
"tools/docker/*", "tools/docker/*",
"tools/editor/*",
"README.md", "README.md",
) )
+9
View File
@@ -8,6 +8,15 @@
] ]
}, },
"tooling": { "tooling": {
"editor": {
"pyright": {
"include": ["modulus", "tools", "tests", "conanfile.py"],
"extraPaths": ["."],
"typeCheckingMode": "off",
"reportMissingImports": false,
"reportMissingModuleSource": false
}
},
"remote": { "remote": {
"name": "khodo", "name": "khodo",
"url": "https://git.thyme-labs.com/api/packages/khodo/conan" "url": "https://git.thyme-labs.com/api/packages/khodo/conan"
+10
View File
@@ -10,6 +10,7 @@ import tarfile
import urllib.parse import urllib.parse
import modulus.config import modulus.config
import modulus.export
build_root = pathlib.Path("build") build_root = pathlib.Path("build")
common_dir = build_root / "build_common" common_dir = build_root / "build_common"
@@ -28,6 +29,8 @@ def main(command):
repo_root = find_repo_root(pathlib.Path.cwd()) repo_root = find_repo_root(pathlib.Path.cwd())
config = modulus.config.load_repo_config(repo_root) config = modulus.config.load_repo_config(repo_root)
context = build_context(repo_root, config) context = build_context(repo_root, config)
if command in {"configure", "lsp"}:
modulus.export.sync_editor_files(repo_root, config)
if command == "configure": if command == "configure":
return configure(context, sys.argv[1:]) return configure(context, sys.argv[1:])
if command == "build": if command == "build":
@@ -78,6 +81,7 @@ class build_context:
result = {} result = {}
targets = self.tooling["targets"] targets = self.tooling["targets"]
for name, target in targets.items(): 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( result[name] = build_target(
name=name, name=name,
profile=self.repo_root / str(target["profile"]), profile=self.repo_root / str(target["profile"]),
@@ -610,9 +614,15 @@ def clear_lsp_cmake_cache(build_dir):
if cmake_files.exists(): if cmake_files.exists():
shutil.rmtree(cmake_files) 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): def configure_lsp_database(context, database):
build_dir = context.repo_root / str(database["build_dir"]) build_dir = context.repo_root / str(database["build_dir"])
source_dir = context.repo_root / str(database.get("source_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) build_dir.mkdir(parents=True, exist_ok=True)
clear_lsp_cmake_cache(build_dir) clear_lsp_cmake_cache(build_dir)
return run( return run(
+42
View File
@@ -77,6 +77,7 @@ test_keys = {
"name", "name",
} }
tooling_keys = { tooling_keys = {
"editor",
"default_target", "default_target",
"targets", "targets",
"docker", "docker",
@@ -255,8 +256,47 @@ def normalize(data):
for target in tooling["targets"].values(): for target in tooling["targets"].values():
target.setdefault("cmake_definitions", {}) target.setdefault("cmake_definitions", {})
validate_config_references(data) validate_config_references(data)
editor = tooling.setdefault("editor", {})
for name, value in default_editor_config(tooling).items():
editor.setdefault(name, value)
return data 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): def validate_raw_config(data):
validate_keys(data, top_level_keys, "modulus.json") validate_keys(data, top_level_keys, "modulus.json")
package = require_mapping(data, "package") package = require_mapping(data, "package")
@@ -340,6 +380,8 @@ def validate_tests(tests):
validate_cmake_name(tests.get("name"), "tests.name", optional=True) validate_cmake_name(tests.get("name"), "tests.name", optional=True)
def validate_tooling(tooling): 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_cmake_name(tooling.get("default_target"), "tooling.default_target", optional=True)
validate_env_name(tooling.get("upload_env"), "tooling.upload_env", optional=True) validate_env_name(tooling.get("upload_env"), "tooling.upload_env", optional=True)
run = tooling.get("run") run = tooling.get("run")
+17 -14
View File
@@ -1,3 +1,4 @@
import json
import pathlib import pathlib
import shutil import shutil
@@ -34,18 +35,18 @@ runtime_ignore_patterns = (
) )
editor_files = { editor_files = {
"clangd.yaml": ".clangd", "clangd": ".clangd",
"clang-format": ".clang-format", "clang_format": ".clang-format",
"pyrightconfig.json": "pyrightconfig.json", "pyright": "pyrightconfig.json",
} }
def sync_project(repo_root, package_root): def sync_project(repo_root, package_root):
repo_root = pathlib.Path(repo_root).resolve() repo_root = pathlib.Path(repo_root).resolve()
package_root = pathlib.Path(package_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_runtime(repo_root, package_root)
sync_tools(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): def sync_runtime(repo_root, package_root):
dst = repo_root / runtime_dir 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")) shutil.copytree(src, dst, ignore=shutil.ignore_patterns("__pycache__", "*.pyc", "*.pyo", "*.pyd"))
chmod_python_tools(dst) chmod_python_tools(dst)
def sync_editor_files(repo_root, package_root): def sync_editor_files(repo_root, config):
src = package_root / "tools" / "editor" editor = config["tooling"]["editor"]
if not src.is_dir(): for name, destination in editor_files.items():
raise RuntimeError(f"Modulus package is missing editor templates: {src}") value = editor[name]
for source_name, destination_name in editor_files.items(): fragments = value if name == "clangd" else [value]
source = src / source_name content = "\n---\n".join(json.dumps(fragment, indent=2, allow_nan=False) for fragment in fragments) + "\n"
if not source.is_file(): if name != "pyright":
raise RuntimeError(f"Modulus package is missing editor template: {source}") content = "# Generated by Modulus from modulus.json tooling.editor.\n" + content
shutil.copy2(source, repo_root / destination_name) 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): def replace_directory(path):
if path.exists(): if path.exists():
-14
View File
@@ -1,14 +0,0 @@
{
"include": [
"modulus.py",
"package/conanfile.py",
"build/build_tools"
],
"extraPaths": [
"build/build_common/modulus",
"."
],
"typeCheckingMode": "off",
"reportMissingImports": false,
"reportMissingModuleSource": false
}
+13
View File
@@ -137,6 +137,19 @@
"default_target": { "default_target": {
"$ref": "#/$defs/cmake_name" "$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": { "upload_env": {
"type": "string", "type": "string",
"pattern": "^[A-Z_][A-Z0-9_]*$" "pattern": "^[A-Z_][A-Z0-9_]*$"
+47 -1
View File
@@ -1,3 +1,4 @@
import json
import os import os
import pathlib import pathlib
import subprocess import subprocess
@@ -69,6 +70,49 @@ class upload_packages_test(unittest.TestCase):
completed = subprocess.run(command, cwd=repo, check=False) completed = subprocess.run(command, cwd=repo, check=False)
self.assertEqual(completed.returncode, 0) 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, "upload_all_packages", return_value=37)
@unittest.mock.patch.object(modulus.cli, "configure_conan_remote", return_value=0) @unittest.mock.patch.object(modulus.cli, "configure_conan_remote", return_value=0)
def test_failed_upload_does_not_force_by_default(self, configure, upload): 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, "run", return_value=0)
@unittest.mock.patch.object(modulus.cli, "in_docker", return_value=True) @unittest.mock.patch.object(modulus.cli, "in_docker", return_value=True)
def test_build_can_target_one_cmake_target(self, docker, run): 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.mkdir(parents=True, exist_ok=True)
(context.default_target.build_dir / "CMakeCache.txt").write_text("", encoding="utf-8") (context.default_target.build_dir / "CMakeCache.txt").write_text("", encoding="utf-8")
(context.default_target.build_dir / "build.ninja").write_text("", encoding="utf-8") (context.default_target.build_dir / "build.ninja").write_text("", encoding="utf-8")
+46 -22
View File
@@ -31,7 +31,6 @@ class modulus_export_test(unittest.TestCase):
for directory in ( for directory in (
"modulus", "modulus",
"tools/docker", "tools/docker",
"tools/editor",
"include/modulus", "include/modulus",
"tests", "tests",
"lib/cmake/modulus", "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" / "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" / "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" / "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 / "include" / "modulus" / "types.hpp").write_text("", encoding="utf-8")
(package / "tests" / "test.cpp").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") (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.assertTrue((repo / "build" / "build_tools" / "docker" / "Dockerfile").is_file())
self.assertFalse((repo / "build" / "build_tools" / "docs").exists()) self.assertFalse((repo / "build" / "build_tools" / "docs").exists())
self.assertFalse((repo / "build" / "build_tools" / "lsp").exists()) self.assertFalse((repo / "build" / "build_tools" / "lsp").exists())
for source_name, destination_name in modulus.export.editor_files.items(): for destination in modulus.export.editor_files.values():
self.assertEqual((repo / destination_name).read_text(encoding="utf-8"), (package / "tools" / "editor" / source_name).read_text(encoding="utf-8")) self.assertTrue((repo / destination).is_file())
runtime = repo / "build" / "build_common" / "modulus" runtime = repo / "build" / "build_common" / "modulus"
self.assertTrue((runtime / "modulus" / "cli.py").is_file()) self.assertTrue((runtime / "modulus" / "cli.py").is_file())
self.assertTrue((runtime / "modulus" / "config.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 / "share").exists())
self.assertFalse((runtime / "bin").exists()) self.assertFalse((runtime / "bin").exists())
def test_root_editor_files_match_export_templates(self): def test_editor_defaults_use_configured_target(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]
config = modulus.config.normalize(self.config()) config = modulus.config.normalize(self.config())
default_target = config["tooling"]["targets"][config["tooling"]["default_target"]] config["tooling"].pop("editor")
template = (root / "tools" / "editor" / "clangd.yaml").read_text(encoding="utf-8") config["tooling"]["targets"][config["tooling"]["default_target"]]["build_dir"] = "build/custom"
self.assertIn(f"CompilationDatabase: {default_target['build_dir']}", template) config = modulus.config.normalize(config)
self.assertIn("Diagnostics:", template) editor = config["tooling"]["editor"]
self.assertIn("ClangTidy:", template) self.assertEqual(editor["clangd"][0]["CompileFlags"]["CompilationDatabase"], "build/custom")
self.assertIn("Remove: ['*']", template) self.assertEqual(editor["clangd"][0]["Diagnostics"]["ClangTidy"]["Remove"], ["*"])
self.assertNotIn("bugprone-*", template) self.assertIn("conanfile.py", editor["pyright"]["include"])
self.assertNotIn("performance-*", template) self.assertNotIn("package/conanfile.py", editor["pyright"]["include"])
self.assertNotIn("readability-*", template)
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): def test_export_requires_packaged_tools(self):
with tempfile.TemporaryDirectory() as repo_dir, tempfile.TemporaryDirectory() as package_dir: with tempfile.TemporaryDirectory() as repo_dir, tempfile.TemporaryDirectory() as package_dir:
-1
View File
@@ -1 +0,0 @@
DisableFormat: true
-9
View File
@@ -1,9 +0,0 @@
CompileFlags:
CompilationDatabase: build/build_x86_debug
Add:
- -Wall
- -Wextra
Diagnostics:
ClangTidy:
Remove: ['*']
-14
View File
@@ -1,14 +0,0 @@
{
"include": [
"modulus.py",
"package/conanfile.py",
"build/build_tools"
],
"extraPaths": [
"build/build_common/modulus",
"."
],
"typeCheckingMode": "off",
"reportMissingImports": false,
"reportMissingModuleSource": false
}