Add regex filters to clangd navigation
This commit is contained in:
13
AGENTS.md
13
AGENTS.md
@@ -69,16 +69,23 @@ flat source tree and extracted components:
|
|||||||
```powershell
|
```powershell
|
||||||
python scripts/dev/clangd_nav.py symbols --file src/app_core/brush_ui.h --name execute_brush
|
python scripts/dev/clangd_nav.py symbols --file src/app_core/brush_ui.h --name execute_brush
|
||||||
python scripts/dev/clangd_nav.py symbols --file src/app_core/brush_ui.h --name-regex "execute_.*preset"
|
python scripts/dev/clangd_nav.py symbols --file src/app_core/brush_ui.h --name-regex "execute_.*preset"
|
||||||
|
python scripts/dev/clangd_nav.py symbols --file src/app_core/document_export.h --detail-regex "Export.*Plan"
|
||||||
python scripts/dev/clangd_nav.py definition --file src/node_panel_brush.cpp --line 511 --column 39
|
python scripts/dev/clangd_nav.py definition --file src/node_panel_brush.cpp --line 511 --column 39
|
||||||
python scripts/dev/clangd_nav.py references --file src/app_core/brush_ui.h --line 783 --column 45
|
python scripts/dev/clangd_nav.py references --file src/app_core/brush_ui.h --line 783 --column 45 --path-regex "src[\\/]app_core"
|
||||||
|
python scripts/dev/clangd_nav.py self-test
|
||||||
```
|
```
|
||||||
|
|
||||||
The helper talks to `clangd` using an existing `compile_commands.json`. It
|
The helper talks to `clangd` using an existing `compile_commands.json`. It
|
||||||
defaults to `out/build/windows-clangcl-asan` and then `out/build/android-arm64`;
|
defaults to `out/build/windows-clangcl-asan` and then `out/build/android-arm64`;
|
||||||
pass `--compile-commands-dir` or set `PP_CLANGD_COMPILE_COMMANDS_DIR` when using
|
pass `--compile-commands-dir` or set `PP_CLANGD_COMPILE_COMMANDS_DIR` when using
|
||||||
another Ninja build tree. Use `--name` and `--max-results` to keep output small.
|
another Ninja build tree. Use `--name` and `--max-results` to keep output small.
|
||||||
Use `--name-regex` for regex filtering against `qualifiedName`; matching is
|
Use `--name-regex` for regex filtering against `qualifiedName`,
|
||||||
case-insensitive by default, and `--no-ignore-case` makes it case-sensitive.
|
`--detail-regex` for symbol detail/signature filtering, and `--path-regex` for
|
||||||
|
definition/declaration/implementation/reference location filtering. Regex
|
||||||
|
matching is case-insensitive by default, and `--no-ignore-case` makes it
|
||||||
|
case-sensitive. Run `python scripts/dev/clangd_nav.py self-test` or the
|
||||||
|
`panopainter_clangd_nav_regex_self_test` CTest before relying on regex behavior
|
||||||
|
after tool changes.
|
||||||
Treat symbol, hover, declaration, definition, and implementation lookups as the
|
Treat symbol, hover, declaration, definition, and implementation lookups as the
|
||||||
reliable path. Reference lookups are riskier because a one-shot clangd process
|
reliable path. Reference lookups are riskier because a one-shot clangd process
|
||||||
may not have a complete project index; the helper refuses reference queries
|
may not have a complete project index; the helper refuses reference queries
|
||||||
|
|||||||
@@ -77,7 +77,10 @@ powershell -ExecutionPolicy Bypass -File scripts\automation\package-smoke.ps1 -P
|
|||||||
cmake --fresh --preset windows-clangcl-asan
|
cmake --fresh --preset windows-clangcl-asan
|
||||||
python scripts/dev/clangd_nav.py symbols --file src/app_core/brush_ui.h --name execute_brush
|
python scripts/dev/clangd_nav.py symbols --file src/app_core/brush_ui.h --name execute_brush
|
||||||
python scripts/dev/clangd_nav.py symbols --file src/app_core/brush_ui.h --name-regex "execute_.*preset"
|
python scripts/dev/clangd_nav.py symbols --file src/app_core/brush_ui.h --name-regex "execute_.*preset"
|
||||||
|
python scripts/dev/clangd_nav.py symbols --file src/app_core/document_export.h --detail-regex "Export.*Plan"
|
||||||
python scripts/dev/clangd_nav.py definition --file src/node_panel_brush.cpp --line 511 --column 39
|
python scripts/dev/clangd_nav.py definition --file src/node_panel_brush.cpp --line 511 --column 39
|
||||||
|
python scripts/dev/clangd_nav.py references --file src/app_core/brush_ui.h --line 783 --column 45 --path-regex "src[\\/]app_core"
|
||||||
|
python scripts/dev/clangd_nav.py self-test
|
||||||
```
|
```
|
||||||
|
|
||||||
Known local toolchain state:
|
Known local toolchain state:
|
||||||
@@ -97,9 +100,15 @@ Known local toolchain state:
|
|||||||
the current `compile_commands.json` from `windows-clangcl-asan`,
|
the current `compile_commands.json` from `windows-clangcl-asan`,
|
||||||
`android-arm64`, or a caller-provided build directory. Symbol, hover,
|
`android-arm64`, or a caller-provided build directory. Symbol, hover,
|
||||||
declaration, definition, and implementation lookups are the reliable use case.
|
declaration, definition, and implementation lookups are the reliable use case.
|
||||||
Symbol listing supports substring filtering with `--name` and regex filtering
|
Symbol listing supports substring filtering with `--name`, regex filtering
|
||||||
against `qualifiedName` with `--name-regex`; regex matching is case-insensitive
|
against `qualifiedName` with `--name-regex`, and symbol detail/signature
|
||||||
by default and can be made case-sensitive with `--no-ignore-case`.
|
regex filtering with `--detail-regex`. Definition, declaration,
|
||||||
|
implementation, and reference location output supports `--path-regex` for
|
||||||
|
path/URI filtering. Regex matching is case-insensitive by default and can be
|
||||||
|
made case-sensitive with `--no-ignore-case`. The helper exposes
|
||||||
|
`python scripts/dev/clangd_nav.py self-test`, also registered as
|
||||||
|
`panopainter_clangd_nav_regex_self_test`, so regex filter behavior can be
|
||||||
|
validated without clangd or a compile database.
|
||||||
Reference lookup is guarded because a one-shot clangd process may not have a
|
Reference lookup is guarded because a one-shot clangd process may not have a
|
||||||
complete project index: pass `--background-index` for broader best-effort
|
complete project index: pass `--background-index` for broader best-effort
|
||||||
results or `--allow-incomplete-references` for explicitly
|
results or `--allow-incomplete-references` for explicitly
|
||||||
|
|||||||
@@ -317,7 +317,10 @@ with JSON automation commands for app document-open routing, app session
|
|||||||
dirty-state and save decisions, creating a `pp_document` model, metadata-only
|
dirty-state and save decisions, creating a `pp_document` model, metadata-only
|
||||||
PPI project loading, and inspecting image signatures, PPI headers, and layout
|
PPI project loading, and inspecting image signatures, PPI headers, and layout
|
||||||
XML; full document/app integration is debt-tracked as DEBT-0010 and full PPI
|
XML; full document/app integration is debt-tracked as DEBT-0010 and full PPI
|
||||||
body parsing is debt-tracked as DEBT-0013.
|
body parsing is debt-tracked as DEBT-0013. Agent code navigation now includes
|
||||||
|
`scripts/dev/clangd_nav.py` with symbol/detail/path regex filters and a
|
||||||
|
`panopainter_clangd_nav_regex_self_test` CTest so broad symbol-family searches
|
||||||
|
can be validated before they guide refactors.
|
||||||
|
|
||||||
Implementation tasks:
|
Implementation tasks:
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,10 @@
|
|||||||
Examples:
|
Examples:
|
||||||
python scripts/dev/clangd_nav.py symbols --file src/app_core/brush_ui.h
|
python scripts/dev/clangd_nav.py symbols --file src/app_core/brush_ui.h
|
||||||
python scripts/dev/clangd_nav.py symbols --file src/app_core/brush_ui.h --name-regex "execute_.*preset"
|
python scripts/dev/clangd_nav.py symbols --file src/app_core/brush_ui.h --name-regex "execute_.*preset"
|
||||||
|
python scripts/dev/clangd_nav.py symbols --file src/app_core/document_export.h --detail-regex "Export.*Plan"
|
||||||
python scripts/dev/clangd_nav.py definition --file src/node_panel_brush.cpp --line 410 --column 30
|
python scripts/dev/clangd_nav.py definition --file src/node_panel_brush.cpp --line 410 --column 30
|
||||||
python scripts/dev/clangd_nav.py references --file src/app_core/brush_ui.h --line 192 --column 43
|
python scripts/dev/clangd_nav.py references --file src/app_core/brush_ui.h --line 192 --column 43 --path-regex "src[\\\\/]app_core"
|
||||||
|
python scripts/dev/clangd_nav.py self-test
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@@ -20,7 +22,7 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from typing import Any
|
from typing import Any, Pattern
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_BUILD_DIRS = (
|
DEFAULT_BUILD_DIRS = (
|
||||||
@@ -301,6 +303,120 @@ def _hover_to_json(result: Any) -> dict[str, Any] | None:
|
|||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def _compile_optional_regex(pattern: str | None, option_name: str, ignore_case: bool) -> Pattern[str] | None:
|
||||||
|
if not pattern:
|
||||||
|
return None
|
||||||
|
flags = re.IGNORECASE if ignore_case else 0
|
||||||
|
try:
|
||||||
|
return re.compile(pattern, flags)
|
||||||
|
except re.error as exc:
|
||||||
|
raise SystemExit(f"invalid {option_name}: {exc}") from exc
|
||||||
|
|
||||||
|
|
||||||
|
def _regex_matches(regex: Pattern[str] | None, value: str) -> bool:
|
||||||
|
return regex is None or regex.search(value) is not None
|
||||||
|
|
||||||
|
|
||||||
|
def _filter_flat_symbols(
|
||||||
|
symbols: list[dict[str, Any]],
|
||||||
|
name_substring: str | None,
|
||||||
|
name_regex: Pattern[str] | None,
|
||||||
|
detail_regex: Pattern[str] | None,
|
||||||
|
) -> list[dict[str, Any]]:
|
||||||
|
filtered = symbols
|
||||||
|
if name_substring:
|
||||||
|
needle = name_substring.lower()
|
||||||
|
filtered = [
|
||||||
|
symbol for symbol in filtered
|
||||||
|
if needle in symbol["qualifiedName"].lower()
|
||||||
|
]
|
||||||
|
if name_regex:
|
||||||
|
filtered = [
|
||||||
|
symbol for symbol in filtered
|
||||||
|
if name_regex.search(symbol["qualifiedName"])
|
||||||
|
]
|
||||||
|
if detail_regex:
|
||||||
|
filtered = [
|
||||||
|
symbol for symbol in filtered
|
||||||
|
if detail_regex.search(symbol.get("detail", ""))
|
||||||
|
]
|
||||||
|
return filtered
|
||||||
|
|
||||||
|
|
||||||
|
def _filter_locations(
|
||||||
|
locations: list[dict[str, Any]],
|
||||||
|
path_regex: Pattern[str] | None,
|
||||||
|
) -> list[dict[str, Any]]:
|
||||||
|
if not path_regex:
|
||||||
|
return locations
|
||||||
|
return [
|
||||||
|
location for location in locations
|
||||||
|
if _regex_matches(path_regex, location.get("path", ""))
|
||||||
|
or _regex_matches(path_regex, location.get("uri", ""))
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _run_self_test() -> int:
|
||||||
|
name_regex = _compile_optional_regex(r"node(panel|dialog)::open_.*", "--name-regex", True)
|
||||||
|
detail_regex = _compile_optional_regex(r"export.*plan", "--detail-regex", True)
|
||||||
|
path_regex = _compile_optional_regex(r"src[\\/]app(_dialogs)?\.cpp$", "--path-regex", True)
|
||||||
|
case_sensitive_regex = _compile_optional_regex(r"Brush", "--name-regex", False)
|
||||||
|
|
||||||
|
symbols = [
|
||||||
|
{
|
||||||
|
"qualifiedName": "NodePanel::open_project",
|
||||||
|
"detail": "void()",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"qualifiedName": "NodeDialog::open_export",
|
||||||
|
"detail": "ExportTargetPlan()",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"qualifiedName": "Brush::open_project",
|
||||||
|
"detail": "BrushPlan()",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
name_matches = _filter_flat_symbols(symbols, None, name_regex, None)
|
||||||
|
detail_matches = _filter_flat_symbols(symbols, None, None, detail_regex)
|
||||||
|
case_sensitive_matches = _filter_flat_symbols(symbols, None, case_sensitive_regex, None)
|
||||||
|
|
||||||
|
locations = [
|
||||||
|
{
|
||||||
|
"path": r"D:\Dev\panopainter\src\app.cpp",
|
||||||
|
"uri": "file:///D:/Dev/panopainter/src/app.cpp",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": r"D:\Dev\panopainter\src\app_dialogs.cpp",
|
||||||
|
"uri": "file:///D:/Dev/panopainter/src/app_dialogs.cpp",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": r"D:\Dev\panopainter\docs\modernization\roadmap.md",
|
||||||
|
"uri": "file:///D:/Dev/panopainter/docs/modernization/roadmap.md",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
path_matches = _filter_locations(locations, path_regex)
|
||||||
|
|
||||||
|
checks = {
|
||||||
|
"nameRegexMatchesAlternationAndWildcard": len(name_matches) == 2,
|
||||||
|
"detailRegexMatchesSymbolDetail": len(detail_matches) == 1
|
||||||
|
and detail_matches[0]["qualifiedName"] == "NodeDialog::open_export",
|
||||||
|
"caseSensitiveRegexHonorsNoIgnoreCase": len(case_sensitive_matches) == 1
|
||||||
|
and case_sensitive_matches[0]["qualifiedName"] == "Brush::open_project",
|
||||||
|
"pathRegexFiltersLocations": len(path_matches) == 2
|
||||||
|
and all("src" in location["path"] for location in path_matches),
|
||||||
|
}
|
||||||
|
ok = all(checks.values())
|
||||||
|
print(json.dumps(
|
||||||
|
{
|
||||||
|
"ok": ok,
|
||||||
|
"command": "self-test",
|
||||||
|
"checks": checks,
|
||||||
|
},
|
||||||
|
indent=2,
|
||||||
|
))
|
||||||
|
return 0 if ok else 1
|
||||||
|
|
||||||
|
|
||||||
def _open_document(client: ClangdClient, file_path: Path) -> None:
|
def _open_document(client: ClangdClient, file_path: Path) -> None:
|
||||||
language_id = "cpp"
|
language_id = "cpp"
|
||||||
if file_path.suffix.lower() in { ".h", ".hpp", ".hh", ".hxx" }:
|
if file_path.suffix.lower() in { ".h", ".hpp", ".hh", ".hxx" }:
|
||||||
@@ -322,16 +438,27 @@ def _open_document(client: ClangdClient, file_path: Path) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def run(args: argparse.Namespace) -> int:
|
def run(args: argparse.Namespace) -> int:
|
||||||
|
if args.command == "self-test":
|
||||||
|
return _run_self_test()
|
||||||
|
|
||||||
|
if not args.file:
|
||||||
|
raise SystemExit("--file is required for clangd navigation commands")
|
||||||
|
|
||||||
|
symbol_filters = [args.name, args.name_regex, args.detail_regex]
|
||||||
|
if any(symbol_filters) and args.command != "symbols":
|
||||||
|
raise SystemExit("--name, --name-regex, and --detail-regex are only supported by the symbols command")
|
||||||
|
if args.path_regex and args.command not in { "definition", "declaration", "implementation", "references" }:
|
||||||
|
raise SystemExit("--path-regex is only supported by location commands")
|
||||||
|
if args.hierarchical and any(symbol_filters):
|
||||||
|
raise SystemExit("--name, --name-regex, and --detail-regex require flat symbols; omit --hierarchical")
|
||||||
|
|
||||||
repo_root = _repo_root()
|
repo_root = _repo_root()
|
||||||
compile_commands_dir = _find_compile_commands_dir(repo_root, args.compile_commands_dir)
|
compile_commands_dir = _find_compile_commands_dir(repo_root, args.compile_commands_dir)
|
||||||
file_path = _resolve_file(repo_root, args.file)
|
file_path = _resolve_file(repo_root, args.file)
|
||||||
|
|
||||||
name_regex = None
|
name_regex = _compile_optional_regex(args.name_regex, "--name-regex", args.ignore_case)
|
||||||
if args.name_regex:
|
detail_regex = _compile_optional_regex(args.detail_regex, "--detail-regex", args.ignore_case)
|
||||||
try:
|
path_regex = _compile_optional_regex(args.path_regex, "--path-regex", args.ignore_case)
|
||||||
name_regex = re.compile(args.name_regex, re.IGNORECASE if args.ignore_case else 0)
|
|
||||||
except re.error as exc:
|
|
||||||
raise SystemExit(f"invalid --name-regex: {exc}") from exc
|
|
||||||
|
|
||||||
if args.command == "references" and not args.background_index and not args.allow_incomplete_references:
|
if args.command == "references" and not args.background_index and not args.allow_incomplete_references:
|
||||||
raise SystemExit(
|
raise SystemExit(
|
||||||
@@ -375,17 +502,7 @@ def run(args: argparse.Namespace) -> int:
|
|||||||
result_count = len(symbols)
|
result_count = len(symbols)
|
||||||
else:
|
else:
|
||||||
flattened = _flatten_symbols(symbols)
|
flattened = _flatten_symbols(symbols)
|
||||||
if args.name:
|
flattened = _filter_flat_symbols(flattened, args.name, name_regex, detail_regex)
|
||||||
needle = args.name.lower()
|
|
||||||
flattened = [
|
|
||||||
symbol for symbol in flattened
|
|
||||||
if needle in symbol["qualifiedName"].lower()
|
|
||||||
]
|
|
||||||
if name_regex:
|
|
||||||
flattened = [
|
|
||||||
symbol for symbol in flattened
|
|
||||||
if name_regex.search(symbol["qualifiedName"])
|
|
||||||
]
|
|
||||||
result_count = len(flattened)
|
result_count = len(flattened)
|
||||||
result, truncated = _limit_results(flattened, args.max_results)
|
result, truncated = _limit_results(flattened, args.max_results)
|
||||||
elif command == "hover":
|
elif command == "hover":
|
||||||
@@ -394,6 +511,7 @@ def run(args: argparse.Namespace) -> int:
|
|||||||
params = _position_params(file_path, args.line, args.column)
|
params = _position_params(file_path, args.line, args.column)
|
||||||
params["context"] = { "includeDeclaration": args.include_declaration }
|
params["context"] = { "includeDeclaration": args.include_declaration }
|
||||||
locations = _locations_to_json(client.request("textDocument/references", params))
|
locations = _locations_to_json(client.request("textDocument/references", params))
|
||||||
|
locations = _filter_locations(locations, path_regex)
|
||||||
result_count = len(locations)
|
result_count = len(locations)
|
||||||
result, truncated = _limit_results(locations, args.max_results)
|
result, truncated = _limit_results(locations, args.max_results)
|
||||||
else:
|
else:
|
||||||
@@ -403,6 +521,7 @@ def run(args: argparse.Namespace) -> int:
|
|||||||
"implementation": "textDocument/implementation",
|
"implementation": "textDocument/implementation",
|
||||||
}[command]
|
}[command]
|
||||||
locations = _locations_to_json(client.request(method, _position_params(file_path, args.line, args.column)))
|
locations = _locations_to_json(client.request(method, _position_params(file_path, args.line, args.column)))
|
||||||
|
locations = _filter_locations(locations, path_regex)
|
||||||
result_count = len(locations)
|
result_count = len(locations)
|
||||||
result, truncated = _limit_results(locations, args.max_results)
|
result, truncated = _limit_results(locations, args.max_results)
|
||||||
|
|
||||||
@@ -417,6 +536,13 @@ def run(args: argparse.Namespace) -> int:
|
|||||||
"not-applicable" if command != "references"
|
"not-applicable" if command != "references"
|
||||||
else ("best-effort-background-index" if args.background_index else "current-translation-unit-only")
|
else ("best-effort-background-index" if args.background_index else "current-translation-unit-only")
|
||||||
),
|
),
|
||||||
|
"filters": {
|
||||||
|
"name": args.name,
|
||||||
|
"nameRegex": args.name_regex,
|
||||||
|
"detailRegex": args.detail_regex,
|
||||||
|
"pathRegex": args.path_regex,
|
||||||
|
"ignoreCase": args.ignore_case,
|
||||||
|
},
|
||||||
"resultCount": result_count,
|
"resultCount": result_count,
|
||||||
"truncated": truncated,
|
"truncated": truncated,
|
||||||
"result": result,
|
"result": result,
|
||||||
@@ -432,9 +558,9 @@ def main(argv: list[str]) -> int:
|
|||||||
parser = argparse.ArgumentParser(description="Navigate C++ symbols through clangd JSON-RPC.")
|
parser = argparse.ArgumentParser(description="Navigate C++ symbols through clangd JSON-RPC.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"command",
|
"command",
|
||||||
choices=("definition", "declaration", "implementation", "references", "hover", "symbols"),
|
choices=("definition", "declaration", "implementation", "references", "hover", "symbols", "self-test"),
|
||||||
)
|
)
|
||||||
parser.add_argument("--file", required=True, help="Source/header file to open.")
|
parser.add_argument("--file", help="Source/header file to open.")
|
||||||
parser.add_argument("--line", type=int, default=1, help="1-based line for position commands.")
|
parser.add_argument("--line", type=int, default=1, help="1-based line for position commands.")
|
||||||
parser.add_argument("--column", type=int, default=1, help="1-based column for position commands.")
|
parser.add_argument("--column", type=int, default=1, help="1-based column for position commands.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
@@ -445,6 +571,8 @@ def main(argv: list[str]) -> int:
|
|||||||
parser.add_argument("--timeout", type=float, default=20.0, help="Request timeout in seconds.")
|
parser.add_argument("--timeout", type=float, default=20.0, help="Request timeout in seconds.")
|
||||||
parser.add_argument("--name", help="Case-insensitive symbol-name filter for symbols command.")
|
parser.add_argument("--name", help="Case-insensitive symbol-name filter for symbols command.")
|
||||||
parser.add_argument("--name-regex", help="Regex filter for symbols command, matched against qualifiedName.")
|
parser.add_argument("--name-regex", help="Regex filter for symbols command, matched against qualifiedName.")
|
||||||
|
parser.add_argument("--detail-regex", help="Regex filter for symbols command, matched against detail.")
|
||||||
|
parser.add_argument("--path-regex", help="Regex filter for definition/declaration/implementation/references paths.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--ignore-case",
|
"--ignore-case",
|
||||||
action=argparse.BooleanOptionalAction,
|
action=argparse.BooleanOptionalAction,
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
find_package(Python3 COMPONENTS Interpreter REQUIRED)
|
||||||
|
|
||||||
|
add_test(NAME panopainter_clangd_nav_regex_self_test
|
||||||
|
COMMAND "${Python3_EXECUTABLE}" "${PROJECT_SOURCE_DIR}/scripts/dev/clangd_nav.py" self-test)
|
||||||
|
set_tests_properties(panopainter_clangd_nav_regex_self_test PROPERTIES
|
||||||
|
LABELS "tooling;desktop-fast")
|
||||||
|
|
||||||
add_library(pp_test_harness INTERFACE)
|
add_library(pp_test_harness INTERFACE)
|
||||||
target_include_directories(pp_test_harness INTERFACE
|
target_include_directories(pp_test_harness INTERFACE
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}")
|
"${CMAKE_CURRENT_SOURCE_DIR}")
|
||||||
|
|||||||
Reference in New Issue
Block a user