modify harness framework
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
import re
|
||||
import xml.etree.ElementTree as ET
|
||||
from pathlib import Path, PureWindowsPath
|
||||
|
||||
from .models import DiscoveryResult, HarnessConfig, ProjectKind, ProjectSelection
|
||||
from .paths import iter_source_files
|
||||
|
||||
|
||||
class DiscoveryError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def validate_csproj(path: Path) -> None:
|
||||
if path.suffix.lower() != ".csproj" or not path.is_file():
|
||||
raise DiscoveryError(f"expected an existing SDK-style .csproj: {path}")
|
||||
try:
|
||||
project = ET.parse(path).getroot()
|
||||
except (ET.ParseError, OSError) as exc:
|
||||
raise DiscoveryError(f"cannot read project {path}: {exc}") from exc
|
||||
name = lambda element: element.tag.rsplit("}", 1)[-1]
|
||||
if name(project) != "Project" or not (
|
||||
project.get("Sdk", "").strip()
|
||||
or any(name(child) == "Sdk" and child.get("Name", "").strip() for child in project)
|
||||
):
|
||||
raise DiscoveryError(f"project must use SDK style; .NET Framework is unsupported: {path}")
|
||||
for element in project.iter():
|
||||
tag = name(element)
|
||||
value = (element.text or "").strip()
|
||||
if tag == "TargetFrameworkVersion" and value:
|
||||
raise DiscoveryError(f".NET Framework metadata is unsupported: {path}")
|
||||
if tag in {"TargetFramework", "TargetFrameworks"}:
|
||||
# Build evaluates inherited properties and expressions. This preflight
|
||||
# rejects explicit incompatible values rather than guessing expansion.
|
||||
for target in value.split(";"):
|
||||
target = target.strip()
|
||||
if target and "$(" not in target and not re.fullmatch(r"net8\.0(?:-[A-Za-z0-9.]+)?", target):
|
||||
raise DiscoveryError(f"only net8.0 targets are supported; found {target!r}: {path}")
|
||||
|
||||
|
||||
def _validate_solution(path: Path, root: Path) -> tuple[Path, ...]:
|
||||
try:
|
||||
content = path.read_text(encoding="utf-8-sig")
|
||||
except (OSError, UnicodeError) as exc:
|
||||
raise DiscoveryError(f"cannot read solution {path}: {exc}") from exc
|
||||
if "Microsoft Visual Studio Solution File" not in content:
|
||||
raise DiscoveryError(f"invalid .sln file: {path}")
|
||||
projects = []
|
||||
folder_types = {"66A26720-8FB5-11D2-AA7E-00C04F688DDE", "2150E333-8FDC-42A3-9474-1A3956D46DE8"}
|
||||
csharp_types = {"FAE04EC0-301F-11D3-BF4B-00C04F79EFBC", "9A19103F-16F7-4668-BE54-9A1E7A4F7556"}
|
||||
for line in content.splitlines():
|
||||
if not line.lstrip().startswith("Project("):
|
||||
continue
|
||||
match = re.match(r'\s*Project\("\{([^}]+)\}"\)\s*=\s*"[^"]*",\s*"([^"]+)"', line)
|
||||
if not match:
|
||||
raise DiscoveryError(f"invalid project entry in solution: {path}")
|
||||
kind, reference = match.groups()
|
||||
if kind.upper() in folder_types:
|
||||
continue
|
||||
if kind.upper() not in csharp_types:
|
||||
raise DiscoveryError(f"unsupported project type in solution: {reference}")
|
||||
windows_path = PureWindowsPath(reference)
|
||||
if windows_path.drive or windows_path.root or Path(reference).is_absolute():
|
||||
raise DiscoveryError(f"solution project must be repository-relative: {reference}")
|
||||
candidate = (path.parent / reference.replace("\\", "/")).resolve()
|
||||
if not candidate.is_relative_to(root):
|
||||
raise DiscoveryError(f"solution project resolves outside repository: {reference}")
|
||||
validate_csproj(candidate)
|
||||
projects.append(candidate)
|
||||
if not projects:
|
||||
raise DiscoveryError(f"solution has no supported C# projects: {path}")
|
||||
return tuple(projects)
|
||||
|
||||
|
||||
def discover_project(root: Path, config: HarnessConfig) -> DiscoveryResult:
|
||||
root = root.resolve()
|
||||
target = config.dotnet.target
|
||||
if target is None:
|
||||
files = tuple(iter_source_files(root))
|
||||
solutions = [path for path in files if path.parent == root and path.suffix.lower() == ".sln"]
|
||||
candidates = solutions or [path for path in files if path.suffix.lower() == ".csproj"]
|
||||
if len(candidates) > 1:
|
||||
raise DiscoveryError("multiple project candidates; set dotnet.target")
|
||||
if candidates:
|
||||
target = candidates[0]
|
||||
elif config.project_type == "dotnet" or any(
|
||||
path.suffix.lower() in {".cs", ".sln", ".slnx", ".vcxproj", ".fsproj", ".vbproj"}
|
||||
for path in files
|
||||
):
|
||||
raise DiscoveryError("no supported C# metadata found; set dotnet.target to a .sln or SDK-style .csproj")
|
||||
else:
|
||||
return DiscoveryResult(None)
|
||||
target = target.resolve()
|
||||
if not target.is_relative_to(root) or not target.is_file():
|
||||
raise DiscoveryError(f"project target must be an existing repository file: {target}")
|
||||
if target.suffix.lower() == ".sln":
|
||||
projects = _validate_solution(target, root)
|
||||
else:
|
||||
validate_csproj(target)
|
||||
projects = (target,)
|
||||
if not config.dotnet.test_projects:
|
||||
raise DiscoveryError("product validation requires an explicit non-empty dotnet.testProjects list")
|
||||
for test_project in config.dotnet.test_projects:
|
||||
if not test_project.resolve().is_relative_to(root):
|
||||
raise DiscoveryError(f"test project resolves outside repository: {test_project}")
|
||||
validate_csproj(test_project)
|
||||
return DiscoveryResult(ProjectSelection(ProjectKind.DOTNET, target, projects))
|
||||
Reference in New Issue
Block a user