Skip to content
Draft
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
32 changes: 32 additions & 0 deletions .devcontainer/codespace.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@
"python.languageServer": "Pylance",
"python.terminal.activateEnvironment": false,
"python.defaultInterpreterPath": "/opt/spack-environments/phlex-ci/.spack-env/view/bin/python",
"esbonio.server.pythonPath": "/opt/spack-environments/phlex-ci/.spack-env/view/bin/python",
"esbonio.logging.level": "debug",
"python.analysis.typeCheckingMode": "basic",
"python.analysis.diagnosticMode": "workspace",
"python.analysis.exclude": [
Expand Down Expand Up @@ -119,5 +121,35 @@
"vscjava.vscode-java-test",
"vscjava.vscode-maven"
]
},
"tasks": {
"version": "2.0.0",
"tasks": [
{
"label": "Generate clang-tidy Problems Log",
"type": "shell",
"command": "python3 /workspaces/phlex/scripts/clang_tidy_fixes_to_problems.py /workspaces/phlex/clang-tidy-fixes.yaml -o /workspaces/phlex/out/clang-tidy-problems.log --workspace-root /workspaces/phlex",
"problemMatcher": []
},
{
"label": "Load clang-tidy Problems",
"type": "shell",
"command": "python3 /workspaces/phlex/scripts/clang_tidy_fixes_to_problems.py /workspaces/phlex/clang-tidy-fixes.yaml -o /workspaces/phlex/out/clang-tidy-problems.log --workspace-root /workspaces/phlex && cat /workspaces/phlex/out/clang-tidy-problems.log",
"problemMatcher": [
{
"owner": "clang-tidy",
"fileLocation": "absolute",
"pattern": {
"regexp": "^(.*):(\\d+):(\\d+):\\s+(warning|error|note):\\s+(.*)$",
"file": 1,
"line": 2,
"column": 3,
"severity": 4,
"message": 5
}
}
]
}
]
}
}
6 changes: 3 additions & 3 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
},
"workspaceFolder": "/workspaces/phlex",
"remoteUser": "root",
"userEnvProbe": "none",
"containerEnv": {
"CMAKE_GENERATOR": "Ninja",
"GH_CONFIG_DIR": "/root/.config/gh",
"DOCKER_HOST": "unix:///tmp/podman.sock",
"CONTAINER_HOST": "unix:///tmp/podman.sock",
Expand All @@ -20,6 +20,8 @@
"source=${localWorkspaceFolder}/../phlex-examples,target=/workspaces/phlex-examples,type=bind",
"source=${localWorkspaceFolder}/../phlex-coding-guidelines,target=/workspaces/phlex-coding-guidelines,type=bind",
"source=${localWorkspaceFolder}/../phlex-spack-recipes,target=/workspaces/phlex-spack-recipes,type=bind",
"source=${localEnv:HOME}/.vscode-remote-user-data,target=/root/.vscode-server-insiders/data/User,type=bind",
"source=${localEnv:HOME}/.vscode-remote-machine-data,target=/root/.vscode-server-insiders/data/Machine,type=bind",
"source=${localEnv:HOME}/.podman-proxy/podman.sock,target=/tmp/podman.sock,type=bind",
"source=${localEnv:HOME}/.aws,target=/root/.aws,type=bind",
"source=${localEnv:HOME}/.config/gh,target=/root/.config/gh,type=bind,readonly",
Expand Down Expand Up @@ -51,8 +53,6 @@
"/opt/spack-environments/phlex-ci/.spack-env/view/lib/python/site-packages"
],
"cmake.defaultConfigurePreset": "default",
"cmake.cmakePath": "${workspaceFolder}/.devcontainer/cmake_wrapper.sh",
"cmake.ctestPath": "${workspaceFolder}/.devcontainer/ctest_wrapper.sh",
"cmake.generator": "Ninja",
"C_Cpp.default.cStandard": "c17",
"C_Cpp.default.cppStandard": "c++23",
Expand Down
27 changes: 11 additions & 16 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,13 @@ When the developer provides HTTPS links in conversation:

## Workspace Environment Setup

> **Note for AI Agents**: The following environment setup instructions apply primarily when working in CI containers (`phlex-ci`, `phlex-dev`) or devcontainers. Human developers may use different local setups (e.g., native macOS, Linux with system packages, or their own Spack configurations).
> **Note for AI Agents**: The following environment setup instructions apply primarily when working outside the devcontainer (e.g., native macOS, Linux with system packages, or custom Spack configurations). In the devcontainer, `/entrypoint.sh` is sourced automatically by `/root/.bashrc`, so the full Spack environment (including `cmake`, `ninja`, `gcc`, etc.) is available in every terminal session without any additional setup.

### setup-env.sh Integration

When working in CI/container environments, always source `setup-env.sh` before executing commands that depend on the build environment:
**Devcontainer**: Do not source `setup-env.sh` or `/entrypoint.sh` in terminal commands — the environment is already configured. Run build tools directly (e.g., `cmake`, `ctest`, `ninja`).

**Outside the devcontainer**, source the appropriate script before commands that depend on the build environment:

- **Repository version**: `srcs/phlex/scripts/setup-env.sh` - Multi-mode environment setup for developers
- Supports standalone repository, multi-project workspace, Spack, and system packages
Expand All @@ -151,13 +153,10 @@ When working in CI/container environments, always source `setup-env.sh` before e
- May exist in multi-project workspace setups
- Can supplement or override repository setup-env.sh

Command execution guidelines:
Command execution guidelines (non-devcontainer only):

- Use `. ./setup-env.sh && <command>` for terminal commands in workspaces with root-level setup-env.sh
- Use `. srcs/phlex/scripts/setup-env.sh && <command>` when working in standalone repository
- Ensure VS Code tasks include appropriate `source` command in their definitions
- Terminal sessions should source the setup script to access build tools (gcc, cmake, ninja, etc.)
- VS Code settings should use absolute paths or `${workspaceFolder}/local` rather than environment variables for IntelliSense configuration
- Always ensure that the terminal's current working directory is appropriate to the command being issued

### Source Directory Symbolic Links
Expand All @@ -174,20 +173,16 @@ If the workspace root contains a `srcs/` directory, it may contain symbolic link

The project uses Spack for dependency management in CI and container development environments:

- **Spack Environments**: The `scripts/setup-env.sh` script automatically activates Spack environments when available
- **Loading Additional Packages**: If you need tools or libraries not loaded by default, use `spack load <package>` to bring them into the environment
- **Common Use Cases**:
- `spack load cmake` - Load CMake if not in current environment
- `spack load gcc` - Load specific GCC compiler
- `spack load ninja` - Load Ninja build tool
- `spack load gcovr` - Load coverage reporting tools
- **Devcontainer**: All Spack packages (cmake, gcc, ninja, gcovr, etc.) are pre-activated via `/entrypoint.sh`; no `spack load` commands are needed.
- **Outside the devcontainer**: The `scripts/setup-env.sh` script automatically activates Spack environments when available
- **Loading Additional Packages** (non-devcontainer): If you need tools or libraries not loaded by default, use `spack load <package>` to bring them into the environment
- **Graceful Degradation**: The build system works with system-installed packages when Spack is unavailable
- **Recipe Repository**: Changes to Spack recipes should be proposed to `Framework-R-D/phlex-spack-recipes`

When suggesting installation of dependencies:

- Prefer sourcing the environment setup script (`scripts/setup-env.sh` or workspace-level `setup-env.sh`) as it handles both Spack and system packages
- For manual installations, provide both Spack (`spack install/load`) and system package manager options
- In the devcontainer, all required tools are already available; no installation steps are needed
- Outside the devcontainer, prefer sourcing the environment setup script (`scripts/setup-env.sh` or workspace-level `setup-env.sh`) as it handles both Spack and system packages
- Consult `scripts/README.md` and `scripts/QUICK_REFERENCE.md` for common patterns

## Text Formatting Standards
Expand Down Expand Up @@ -290,7 +285,7 @@ All Markdown files must strictly follow these markdownlint rules:

### Build and Test

- **Environment**: Always source `setup-env.sh` before building or testing. This applies to all environments (Dev Container, local machine, HPC).
- **Environment**: Always source `setup-env.sh` before building or testing outside the devcontainer. In the devcontainer, the environment is already configured — run build tools directly.
- **Configuration**:
- **Presets**: Prefer `CMakePresets.json` workflows (e.g., `cmake --preset default`).
- **Generator**: Prefer `Ninja` over `Makefiles` when available (`-G Ninja`).
Expand Down
8 changes: 8 additions & 0 deletions form/.clang-tidy
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
# Clang-tidy configuration for Phlex project
# Enforces C++ Core Guidelines and modern C++23 best practices

InheritParentConfig: true

Checks: >
-readability-identifier-naming
1 change: 1 addition & 0 deletions phlex.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
],
"settings": {
"files.associations": {
".yamllint": "yaml",
"*.yml": "yaml",
"*.yaml": "yaml"
},
Expand Down
2 changes: 2 additions & 0 deletions phlex/app/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,5 @@ if(ENABLE_PERFETTO)
endif()

set_target_properties(phlex PROPERTIES INSTALL_RPATH "$ORIGIN/../lib")

# Extra line to trigger PR checks
218 changes: 218 additions & 0 deletions scripts/clang_tidy_fixes_to_problems.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
#!/usr/bin/env python3

"""Convert clang-tidy export-fixes YAML into compiler-style diagnostics.

The output format is compatible with VS Code problem matchers such as "$gcc":

/abs/path/file.cpp:line:column: warning: message [check-name]

This script intentionally uses a lightweight line-based parser so it does not
depend on PyYAML.
"""

from __future__ import annotations

import argparse
from dataclasses import dataclass
from pathlib import Path
import re
import sys


@dataclass
class Diagnostic:
check: str = "clang-tidy"
message: str = ""
level: str = "warning"
file_path: str | None = None
file_offset: int | None = None


def _strip_yaml_string(value: str) -> str:
value = value.strip()
if len(value) >= 2 and value[0] == "'" and value[-1] == "'":
# YAML single-quoted escape sequence: '' -> '
return value[1:-1].replace("''", "'")
return value


def _parse_kv(line: str) -> tuple[str, str] | None:
match = re.match(r"^\s*([^:]+):\s*(.*)$", line)
if not match:
return None
return match.group(1).strip(), match.group(2).rstrip("\n")


def parse_clang_tidy_fixes(text: str) -> tuple[str | None, list[Diagnostic]]:
main_source_file: str | None = None
diagnostics: list[Diagnostic] = []

current: Diagnostic | None = None
in_diag_message = False

for raw_line in text.splitlines():
line = raw_line.rstrip("\n")

if line.startswith("MainSourceFile:"):
kv = _parse_kv(line)
if kv:
main_source_file = _strip_yaml_string(kv[1])
continue

if re.match(r"^\s*-\s+DiagnosticName:\s+", line):
if current is not None:
diagnostics.append(current)
check_name = re.sub(r"^\s*-\s+DiagnosticName:\s+", "", line)
current = Diagnostic(check=_strip_yaml_string(check_name).strip())
in_diag_message = False
continue

if current is None:
continue

if re.match(r"^\s*DiagnosticMessage:\s*$", line):
in_diag_message = True
continue

if re.match(r"^\s*Notes:\s*$", line):
# Notes are supplementary and do not define the primary location.
in_diag_message = False
continue

kv = _parse_kv(line)
if not kv:
continue

key, value = kv

if in_diag_message:
if key == "Message":
current.message = _strip_yaml_string(value)
elif key == "FilePath":
current.file_path = _strip_yaml_string(value)
elif key == "FileOffset":
try:
current.file_offset = int(value.strip())
except ValueError:
current.file_offset = None
continue

if key == "Level":
level = _strip_yaml_string(value).strip().lower()
if level:
current.level = level

if current is not None:
diagnostics.append(current)

return main_source_file, diagnostics


def apply_path_map(path: str, mappings: list[tuple[str, str]]) -> str:
normalized = path
for old, new in mappings:
if normalized.startswith(old):
normalized = new + normalized[len(old) :]
break
return normalized


def offset_to_line_col(path: Path, offset: int) -> tuple[int, int]:
try:
data = path.read_bytes()
except OSError:
return 1, 1

if not data:
return 1, 1

bounded = max(0, min(offset, len(data)))
line = data.count(b"\n", 0, bounded) + 1
last_newline = data.rfind(b"\n", 0, bounded)
if last_newline < 0:
col = bounded + 1
else:
col = bounded - last_newline
return line, max(col, 1)


def parse_path_map(items: list[str]) -> list[tuple[str, str]]:
mappings: list[tuple[str, str]] = []
for item in items:
if "=" not in item:
raise ValueError(f"Invalid --path-map value '{item}'. Expected OLD=NEW.")
old, new = item.split("=", 1)
mappings.append((old, new))
return mappings


def build_arg_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Convert clang-tidy export-fixes YAML into compiler-style diagnostics."
)
parser.add_argument("input", type=Path, help="Path to clang-tidy-fixes.yaml")
parser.add_argument(
"-o",
"--output",
type=Path,
required=True,
help="Output text file with compiler-style diagnostics",
)
parser.add_argument(
"--workspace-root",
type=Path,
default=Path.cwd(),
help="Workspace root used by default path mapping",
)
parser.add_argument(
"--path-map",
action="append",
default=[],
help="Path mapping in OLD=NEW form. Can be specified multiple times.",
)
return parser


def main() -> int:
args = build_arg_parser().parse_args()

text = args.input.read_text(encoding="utf-8")
main_source, diagnostics = parse_clang_tidy_fixes(text)

default_mappings = [
("/__w/phlex/phlex/phlex-src", str(args.workspace_root.resolve())),
("/__w/phlex/phlex/phlex-build", str((args.workspace_root / "build").resolve())),
]
extra_mappings = parse_path_map(args.path_map)
mappings = extra_mappings + default_mappings

lines: list[str] = []
for diag in diagnostics:
file_path = diag.file_path or main_source
if not file_path:
# Skip diagnostics with no usable location.
continue

mapped = apply_path_map(file_path, mappings)
resolved = Path(mapped)

offset = diag.file_offset if diag.file_offset is not None else 0
line, col = offset_to_line_col(resolved, offset)

message = diag.message or "clang-tidy diagnostic"
check = diag.check or "clang-tidy"
severity = diag.level if diag.level in {"error", "warning", "note"} else "warning"
lines.append(f"{resolved}:{line}:{col}: {severity}: {message} [{check}]")

args.output.parent.mkdir(parents=True, exist_ok=True)
output_text = "\n".join(lines)
if output_text:
output_text += "\n"
args.output.write_text(output_text, encoding="utf-8")

print(f"Wrote {len(lines)} diagnostics to {args.output}")
return 0


if __name__ == "__main__":
sys.exit(main())
8 changes: 8 additions & 0 deletions test/form/.clang-tidy
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
# Clang-tidy configuration for Phlex project
# Enforces C++ Core Guidelines and modern C++23 best practices

InheritParentConfig: true

Checks: >
-readability-identifier-naming
Loading
Loading