Skip to content

Commit c5b3687

Browse files
Ubuntuclaude
andcommitted
refactor: remove zero-config logic from java-config-redesign branch
Zero-config Java support (auto-detection from build files, codeflash.toml elimination) is handled separately in cf-java-zero-config-strategy. This commit strips those changes, keeping only bug fixes: - JFR parser, ReplayHelper, instrumentation, replay tests - Multi-module test root resolution - JUnit 4/5 test framework detection - add_help=False for optimize subparser Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 970c9f8 commit c5b3687

17 files changed

Lines changed: 235 additions & 1306 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[tool.codeflash]
2+
module-root = "src/main/java"
3+
tests-root = "src/test/java"
4+
formatter-cmds = []
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Codeflash configuration for Java project
2+
3+
[tool.codeflash]
4+
module-root = "src/main/java"
5+
tests-root = "src/test/java"
6+
formatter-cmds = []

codeflash/cli_cmds/cli.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -190,12 +190,6 @@ def process_pyproject_config(args: Namespace) -> Namespace:
190190
if args.benchmarks_root:
191191
args.benchmarks_root = Path(args.benchmarks_root).resolve()
192192
args.test_project_root = project_root_from_module_root(args.tests_root, pyproject_file_path)
193-
194-
if is_java_project and pyproject_file_path.is_dir():
195-
# For Java projects, pyproject_file_path IS the project root directory (not a file).
196-
# Override project_root which may have resolved to a sub-module.
197-
args.project_root = pyproject_file_path.resolve()
198-
args.test_project_root = pyproject_file_path.resolve()
199193
if is_LSP_enabled():
200194
args.all = None
201195
return args
@@ -214,6 +208,8 @@ def project_root_from_module_root(module_root: Path, pyproject_file_path: Path)
214208
return current.resolve()
215209
if (current / "build.gradle").exists() or (current / "build.gradle.kts").exists():
216210
return current.resolve()
211+
if (current / "codeflash.toml").exists():
212+
return current.resolve()
217213
current = current.parent
218214

219215
return module_root.parent.resolve()

codeflash/code_utils/config_parser.py

Lines changed: 24 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -12,29 +12,8 @@
1212
ALL_CONFIG_FILES: dict[Path, dict[str, Path]] = {}
1313

1414

15-
def _try_parse_java_build_config() -> tuple[dict[str, Any], Path] | None:
16-
"""Detect Java project from build files and parse config from pom.xml/gradle.properties.
17-
18-
Returns (config_dict, project_root) if a Java project is found, None otherwise.
19-
"""
20-
dir_path = Path.cwd()
21-
while dir_path != dir_path.parent:
22-
if (
23-
(dir_path / "pom.xml").exists()
24-
or (dir_path / "build.gradle").exists()
25-
or (dir_path / "build.gradle.kts").exists()
26-
):
27-
from codeflash.languages.java.build_tools import parse_java_project_config
28-
29-
config = parse_java_project_config(dir_path)
30-
if config is not None:
31-
return config, dir_path
32-
dir_path = dir_path.parent
33-
return None
34-
35-
3615
def find_pyproject_toml(config_file: Path | None = None) -> Path:
37-
# Find the pyproject.toml file on the root of the project
16+
# Find the pyproject.toml or codeflash.toml file on the root of the project
3817

3918
if config_file is not None:
4019
config_file = Path(config_file)
@@ -50,13 +29,21 @@ def find_pyproject_toml(config_file: Path | None = None) -> Path:
5029
# see if it was encountered before in search
5130
if cur_path in PYPROJECT_TOML_CACHE:
5231
return PYPROJECT_TOML_CACHE[cur_path]
32+
# map current path to closest file - check both pyproject.toml and codeflash.toml
5333
while dir_path != dir_path.parent:
34+
# First check pyproject.toml (Python projects)
5435
config_file = dir_path / "pyproject.toml"
5536
if config_file.exists():
5637
PYPROJECT_TOML_CACHE[cur_path] = config_file
5738
return config_file
39+
# Then check codeflash.toml (Java/other projects)
40+
config_file = dir_path / "codeflash.toml"
41+
if config_file.exists():
42+
PYPROJECT_TOML_CACHE[cur_path] = config_file
43+
return config_file
44+
# Search in parent directories
5845
dir_path = dir_path.parent
59-
msg = f"Could not find pyproject.toml in the current directory {Path.cwd()} or any of the parent directories. Please create it by running `codeflash init`, or pass the path to the config file with the --config-file argument."
46+
msg = f"Could not find pyproject.toml or codeflash.toml in the current directory {Path.cwd()} or any of the parent directories. Please create it by running `codeflash init`, or pass the path to the config file with the --config-file argument."
6047

6148
raise ValueError(msg) from None
6249

@@ -103,34 +90,33 @@ def find_conftest_files(test_paths: list[Path]) -> list[Path]:
10390
return list(list_of_conftest_files)
10491

10592

93+
# TODO for claude: There should be different functions to parse it per language, which should be chosen during runtime
10694
def parse_config_file(
10795
config_file_path: Path | None = None, override_formatter_check: bool = False
10896
) -> tuple[dict[str, Any], Path]:
109-
# Detect all config sources — Java, package.json, pyproject.toml
110-
java_result = _try_parse_java_build_config() if config_file_path is None else None
11197
package_json_path = find_package_json(config_file_path)
11298
pyproject_toml_path = find_closest_config_file("pyproject.toml") if config_file_path is None else None
99+
codeflash_toml_path = find_closest_config_file("codeflash.toml") if config_file_path is None else None
113100

114-
# Use Java config only if no closer JS/Python config exists (monorepo support).
115-
# In a monorepo with a parent pom.xml and a child package.json, the closer config wins.
116-
if java_result is not None:
117-
java_depth = len(java_result[1].parts)
118-
has_closer = (package_json_path is not None and len(package_json_path.parent.parts) >= java_depth) or (
119-
pyproject_toml_path is not None and len(pyproject_toml_path.parent.parts) >= java_depth
120-
)
121-
if not has_closer:
122-
return java_result
101+
# Pick the closest toml config (pyproject.toml or codeflash.toml).
102+
# Java projects use codeflash.toml; Python projects use pyproject.toml.
103+
closest_toml_path = None
104+
if pyproject_toml_path and codeflash_toml_path:
105+
closest_toml_path = max(pyproject_toml_path, codeflash_toml_path, key=lambda p: len(p.parent.parts))
106+
else:
107+
closest_toml_path = pyproject_toml_path or codeflash_toml_path
123108

124109
# When both config files exist, prefer the one closer to CWD.
125110
# This prevents a parent-directory package.json (e.g., monorepo root)
126-
# from overriding a closer pyproject.toml.
111+
# from overriding a closer pyproject.toml or codeflash.toml.
127112
use_package_json = False
128113
if package_json_path:
129-
if pyproject_toml_path is None:
114+
if closest_toml_path is None:
130115
use_package_json = True
131116
else:
117+
# Compare depth: more path parts = closer to CWD = more specific
132118
package_json_depth = len(package_json_path.parent.parts)
133-
toml_depth = len(pyproject_toml_path.parent.parts)
119+
toml_depth = len(closest_toml_path.parent.parts)
134120
use_package_json = package_json_depth >= toml_depth
135121

136122
if use_package_json:
@@ -174,7 +160,7 @@ def parse_config_file(
174160
if config == {} and lsp_mode:
175161
return {}, config_file_path
176162

177-
# Preserve language field if present (important for JS/TS projects)
163+
# Preserve language field if present (important for Java/JS projects using codeflash.toml)
178164
# default values:
179165
path_keys = ["module-root", "tests-root", "benchmarks-root"]
180166
path_list_keys = ["ignore-paths"]

codeflash/languages/java/build_tools.py

Lines changed: 1 addition & 214 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@
1010
import xml.etree.ElementTree as ET
1111
from dataclasses import dataclass
1212
from enum import Enum
13-
from pathlib import Path
14-
from typing import Any
13+
from pathlib import Path # noqa: TC003 — used at runtime
1514

1615
logger = logging.getLogger(__name__)
1716

@@ -344,218 +343,6 @@ def _parse_surefire_reports(surefire_dir: Path) -> tuple[int, int, int, int]:
344343
return tests_run, failures, errors, skipped
345344

346345

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-
559346
def find_test_root(project_root: Path) -> Path | None:
560347
"""Find the test root directory for a Java project.
561348

0 commit comments

Comments
 (0)