Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/fprime/util/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def skip_build_loading(parsed):
"""Determines if the build load step should be skipped. Commands that do not require a build object
should manually be added here by the developer.
"""
if parsed.command == "version-check":
if parsed.command in ["version-check", "format"]:
return True
return False

Expand All @@ -73,7 +73,6 @@ def skip_build_cache_validation(parsed):
if parsed.command in [
"purge",
"info",
"format",
]:
return True
if parsed.command == "new" and parsed.new_deployment:
Expand Down
19 changes: 13 additions & 6 deletions src/fprime/util/code_formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import shutil
import subprocess
from pathlib import Path
from typing import Dict, List, Tuple
from typing import Dict, List, Optional, Tuple

from fprime.fbuild.target import ExecutableAction, TargetScope

Expand All @@ -32,7 +32,7 @@
class ClangFormatter(ExecutableAction):
"""Class encapsulating the clang-format logic for fprime-util"""

def __init__(self, executable: str, style_file: "Path", options: Dict):
def __init__(self, executable: str, style_file: "Optional[Path]", options: Dict):
super().__init__(TargetScope.LOCAL)
self.executable = executable
self.style_file = style_file
Expand Down Expand Up @@ -99,12 +99,13 @@ def execute(
args (Tuple[Dict[str, str], List[str]]): extra arguments to supply to the utility
"""
combined_env = os.environ.copy()
combined_env.update(builder.settings.get("environment", {}))
if builder is not None:
combined_env.update(builder.settings.get("environment", {}))

if len(self._files_to_format) == 0:
print("[INFO] No files were formatted.")
return 0
if not self.style_file.is_file():
if self.style_file is not None and not self.style_file.is_file():
print(
f"[ERROR] No .clang-format file found in {self.style_file.parent}. "
"Override location with --pass-through --style=file:<path>."
Expand All @@ -129,7 +130,13 @@ def execute(
print(f"[INFO] {self.executable}")
print("[INFO] Clang format arguments:")
print(f"[INFO] {clang_args[1:]}")
print("[INFO] Clang format style file:")
print(f"[INFO] {self.style_file}")
if self.style_file is not None:
print("[INFO] Clang format style file:")
print(f"[INFO] {self.style_file}")
else:
print(
"[INFO] Clang format style file: discovered by clang-format "
"(--style=file)"
)
status = subprocess.run(clang_args, env=combined_env)
return status.returncode
7 changes: 5 additions & 2 deletions src/fprime/util/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ def run_code_format(
"""Runs code formatting using clang-format

Args:
build: used to retrieve .clang-format file
build: unused; format runs without a build cache (may be None)
parsed: parsed input arguments
__: unused cmake_args
___: unused make_args
Expand All @@ -177,9 +177,12 @@ def run_code_format(
"validate_extensions": not parsed.force,
"check": parsed.check,
}
# No explicit style file: clang-format is invoked with --style=file, which
# discovers the nearest .clang-format from each input file's directory
# (projects and libraries provide their own).
clang_formatter = ClangFormatter(
"clang-format",
build.settings.get("framework_path", Path(".")) / ".clang-format",
None,
options,
)
if not clang_formatter.is_supported():
Expand Down
55 changes: 55 additions & 0 deletions test/fprime/util/test_code_formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
Tests for fprime.util.code_formatter
"""

import argparse
from pathlib import Path

import shutil
from unittest.mock import MagicMock

import pytest
from fprime.util.code_formatter import ClangFormatter
from fprime.util.commands import run_code_format


def test_init():
Expand Down Expand Up @@ -143,3 +145,56 @@ def test_execute_check_pass(tmp_path, mock_build, style_file):

result = formatter.execute(mock_build, tmp_path, ({}, []))
assert result == 0


def test_execute_no_build(tmp_path, style_file):
"""Test that execute works when no build object is provided (library case)"""
malformed_src = DATA_DIR / "malformed.cpp"
malformed_dst = tmp_path / "malformed.cpp"
shutil.copy(malformed_src, malformed_dst)

options = {"backup": False, "verbose": False, "quiet": True, "check": False}
formatter = ClangFormatter("clang-format", style_file, options)
formatter.stage_file(malformed_dst)

# build=None must not raise (libraries run format without a build cache)
result = formatter.execute(None, tmp_path, ({}, []))
assert result == 0


def test_run_code_format_in_library(tmp_path, monkeypatch):
"""End-to-end: format a library directory with no settings.ini, using its own style file"""
if shutil.which("clang-format") is None:
pytest.skip("clang-format executable not available")

library_root = tmp_path / "fprime-zephyr"
svc_dir = library_root / "Svc"
svc_dir.mkdir(parents=True)
(library_root / ".clang-format").write_text("BasedOnStyle: LLVM\n")

malformed = svc_dir / "Component.cpp"
shutil.copy(DATA_DIR / "malformed.cpp", malformed)

monkeypatch.chdir(library_root)
parsed = argparse.Namespace(
root=None,
path=Path.cwd(),
quiet=True,
verbose=False,
backup=False,
force=False,
check=False,
allow_extension=[],
stdin=False,
files=[],
dirs=[Path("./Svc")],
exclude=[],
pass_through=[],
)

# build is None: libraries run format without a build cache
result = run_code_format(None, parsed, {}, {}, [])
assert result == 0

well_formed_content = (DATA_DIR / "well-formed.cpp").read_text()
assert malformed.read_text() == well_formed_content
Loading