|
10 | 10 | import xml.etree.ElementTree as ET |
11 | 11 | from dataclasses import dataclass |
12 | 12 | from enum import Enum |
13 | | -from pathlib import Path |
14 | | -from typing import Any |
| 13 | +from pathlib import Path # noqa: TC003 — used at runtime |
15 | 14 |
|
16 | 15 | logger = logging.getLogger(__name__) |
17 | 16 |
|
@@ -344,218 +343,6 @@ def _parse_surefire_reports(surefire_dir: Path) -> tuple[int, int, int, int]: |
344 | 343 | return tests_run, failures, errors, skipped |
345 | 344 |
|
346 | 345 |
|
347 | | -def parse_java_project_config(project_root: Path) -> dict[str, Any] | None: |
348 | | - """Parse codeflash config from Maven/Gradle build files. |
349 | | -
|
350 | | - Reads codeflash.* properties from pom.xml <properties> or gradle.properties, |
351 | | - then fills in defaults from auto-detected build tool conventions. |
352 | | -
|
353 | | - Returns None if no Java build tool is detected. |
354 | | - """ |
355 | | - build_tool = detect_build_tool(project_root) |
356 | | - if build_tool == BuildTool.UNKNOWN: |
357 | | - return None |
358 | | - |
359 | | - # Read explicit codeflash properties from build files |
360 | | - user_config: dict[str, str] = {} |
361 | | - if build_tool == BuildTool.MAVEN: |
362 | | - user_config = _read_maven_codeflash_properties(project_root) |
363 | | - elif build_tool == BuildTool.GRADLE: |
364 | | - user_config = _read_gradle_codeflash_properties(project_root) |
365 | | - |
366 | | - # Auto-detect defaults — for multi-module Maven projects, scan module pom.xml files |
367 | | - source_root = find_source_root(project_root) |
368 | | - test_root = find_test_root(project_root) |
369 | | - |
370 | | - if build_tool == BuildTool.MAVEN: |
371 | | - source_from_modules, test_from_modules = _detect_roots_from_maven_modules(project_root) |
372 | | - # Module-level pom.xml declarations are more precise than directory-name heuristics |
373 | | - if source_from_modules is not None: |
374 | | - source_root = source_from_modules |
375 | | - if test_from_modules is not None: |
376 | | - test_root = test_from_modules |
377 | | - |
378 | | - # Build the config dict matching the format expected by the rest of codeflash |
379 | | - config: dict[str, Any] = { |
380 | | - "language": "java", |
381 | | - "module_root": str( |
382 | | - (project_root / user_config["moduleRoot"]).resolve() |
383 | | - if "moduleRoot" in user_config |
384 | | - else (source_root or project_root / "src" / "main" / "java") |
385 | | - ), |
386 | | - "tests_root": str( |
387 | | - (project_root / user_config["testsRoot"]).resolve() |
388 | | - if "testsRoot" in user_config |
389 | | - else (test_root or project_root / "src" / "test" / "java") |
390 | | - ), |
391 | | - "pytest_cmd": "pytest", |
392 | | - "git_remote": user_config.get("gitRemote", "origin"), |
393 | | - "disable_telemetry": user_config.get("disableTelemetry", "false").lower() == "true", |
394 | | - "disable_imports_sorting": False, |
395 | | - "override_fixtures": False, |
396 | | - "benchmark": False, |
397 | | - "formatter_cmds": [], |
398 | | - "ignore_paths": [], |
399 | | - } |
400 | | - |
401 | | - if "ignorePaths" in user_config: |
402 | | - config["ignore_paths"] = [ |
403 | | - str((project_root / p.strip()).resolve()) for p in user_config["ignorePaths"].split(",") if p.strip() |
404 | | - ] |
405 | | - |
406 | | - if "formatterCmds" in user_config: |
407 | | - config["formatter_cmds"] = [cmd.strip() for cmd in user_config["formatterCmds"].split(",") if cmd.strip()] |
408 | | - |
409 | | - return config |
410 | | - |
411 | | - |
412 | | -def _read_maven_codeflash_properties(project_root: Path) -> dict[str, str]: |
413 | | - """Read codeflash.* properties from pom.xml <properties> section.""" |
414 | | - pom_path = project_root / "pom.xml" |
415 | | - if not pom_path.exists(): |
416 | | - return {} |
417 | | - |
418 | | - try: |
419 | | - tree = _safe_parse_xml(pom_path) |
420 | | - root = tree.getroot() |
421 | | - ns = {"m": "http://maven.apache.org/POM/4.0.0"} |
422 | | - |
423 | | - result: dict[str, str] = {} |
424 | | - for props in [root.find("m:properties", ns), root.find("properties")]: |
425 | | - if props is None: |
426 | | - continue |
427 | | - for child in props: |
428 | | - tag = child.tag |
429 | | - # Strip Maven namespace prefix |
430 | | - if "}" in tag: |
431 | | - tag = tag.split("}", 1)[1] |
432 | | - if tag.startswith("codeflash.") and child.text: |
433 | | - key = tag[len("codeflash.") :] |
434 | | - result[key] = child.text.strip() |
435 | | - return result |
436 | | - except Exception: |
437 | | - logger.debug("Failed to read codeflash properties from pom.xml", exc_info=True) |
438 | | - return {} |
439 | | - |
440 | | - |
441 | | -def _read_gradle_codeflash_properties(project_root: Path) -> dict[str, str]: |
442 | | - """Read codeflash.* properties from gradle.properties.""" |
443 | | - props_path = project_root / "gradle.properties" |
444 | | - if not props_path.exists(): |
445 | | - return {} |
446 | | - |
447 | | - result: dict[str, str] = {} |
448 | | - try: |
449 | | - with props_path.open("r", encoding="utf-8") as f: |
450 | | - for line in f: |
451 | | - stripped = line.strip() |
452 | | - if stripped.startswith("#") or "=" not in stripped: |
453 | | - continue |
454 | | - key, value = stripped.split("=", 1) |
455 | | - key = key.strip() |
456 | | - if key.startswith("codeflash."): |
457 | | - result[key[len("codeflash.") :]] = value.strip() |
458 | | - return result |
459 | | - except Exception: |
460 | | - logger.debug("Failed to read codeflash properties from gradle.properties", exc_info=True) |
461 | | - return {} |
462 | | - |
463 | | - |
464 | | -def _detect_roots_from_maven_modules(project_root: Path) -> tuple[Path | None, Path | None]: |
465 | | - """Scan Maven module pom.xml files for custom sourceDirectory/testSourceDirectory. |
466 | | -
|
467 | | - For multi-module projects like aerospike (client/, test/, benchmarks/), |
468 | | - finds the main source module and test module by parsing each module's build config. |
469 | | - """ |
470 | | - pom_path = project_root / "pom.xml" |
471 | | - if not pom_path.exists(): |
472 | | - return None, None |
473 | | - |
474 | | - try: |
475 | | - tree = _safe_parse_xml(pom_path) |
476 | | - root = tree.getroot() |
477 | | - ns = {"m": "http://maven.apache.org/POM/4.0.0"} |
478 | | - |
479 | | - # Find <modules> to get module names |
480 | | - modules: list[str] = [] |
481 | | - for modules_elem in [root.find("m:modules", ns), root.find("modules")]: |
482 | | - if modules_elem is not None: |
483 | | - for mod in modules_elem: |
484 | | - if mod.text: |
485 | | - modules.append(mod.text.strip()) |
486 | | - |
487 | | - if not modules: |
488 | | - return None, None |
489 | | - |
490 | | - # Collect candidate source and test roots with Java file counts |
491 | | - source_candidates: list[tuple[Path, int]] = [] |
492 | | - test_root: Path | None = None |
493 | | - |
494 | | - skip_modules = {"example", "examples", "benchmark", "benchmarks", "demo", "sample", "samples"} |
495 | | - |
496 | | - for module_name in modules: |
497 | | - module_pom = project_root / module_name / "pom.xml" |
498 | | - if not module_pom.exists(): |
499 | | - continue |
500 | | - |
501 | | - # Modules named "test" are test modules, not source modules |
502 | | - is_test_module = "test" in module_name.lower() |
503 | | - |
504 | | - try: |
505 | | - mod_tree = _safe_parse_xml(module_pom) |
506 | | - mod_root = mod_tree.getroot() |
507 | | - |
508 | | - for build in [mod_root.find("m:build", ns), mod_root.find("build")]: |
509 | | - if build is None: |
510 | | - continue |
511 | | - |
512 | | - for src_elem in [build.find("m:sourceDirectory", ns), build.find("sourceDirectory")]: |
513 | | - if src_elem is not None and src_elem.text: |
514 | | - src_text = src_elem.text.replace("${project.basedir}", str(project_root / module_name)) |
515 | | - src_path = Path(src_text) |
516 | | - if not src_path.is_absolute(): |
517 | | - src_path = project_root / module_name / src_path |
518 | | - if src_path.exists(): |
519 | | - if is_test_module and test_root is None: |
520 | | - test_root = src_path |
521 | | - elif module_name.lower() not in skip_modules: |
522 | | - java_count = sum(1 for _ in src_path.rglob("*.java")) |
523 | | - if java_count > 0: |
524 | | - source_candidates.append((src_path, java_count)) |
525 | | - |
526 | | - for test_elem in [build.find("m:testSourceDirectory", ns), build.find("testSourceDirectory")]: |
527 | | - if test_elem is not None and test_elem.text: |
528 | | - test_text = test_elem.text.replace("${project.basedir}", str(project_root / module_name)) |
529 | | - test_path = Path(test_text) |
530 | | - if not test_path.is_absolute(): |
531 | | - test_path = project_root / module_name / test_path |
532 | | - if test_path.exists() and test_root is None: |
533 | | - test_root = test_path |
534 | | - |
535 | | - # Also check standard module layouts |
536 | | - if module_name.lower() not in skip_modules and not is_test_module: |
537 | | - std_src = project_root / module_name / "src" / "main" / "java" |
538 | | - if std_src.exists(): |
539 | | - java_count = sum(1 for _ in std_src.rglob("*.java")) |
540 | | - if java_count > 0: |
541 | | - source_candidates.append((std_src, java_count)) |
542 | | - |
543 | | - if test_root is None: |
544 | | - std_test = project_root / module_name / "src" / "test" / "java" |
545 | | - if std_test.exists() and any(std_test.rglob("*.java")): |
546 | | - test_root = std_test |
547 | | - |
548 | | - except Exception: |
549 | | - continue |
550 | | - |
551 | | - # Pick the source root with the most Java files (likely the main library) |
552 | | - source_root = max(source_candidates, key=lambda x: x[1])[0] if source_candidates else None |
553 | | - return source_root, test_root |
554 | | - |
555 | | - except Exception: |
556 | | - return None, None |
557 | | - |
558 | | - |
559 | 346 | def find_test_root(project_root: Path) -> Path | None: |
560 | 347 | """Find the test root directory for a Java project. |
561 | 348 |
|
|
0 commit comments