Skip to content

Commit 3f95ff6

Browse files
misrasaurabh1claude
andcommitted
feat: eliminate codeflash.toml — auto-detect Java config from build files
Java projects no longer need a standalone config file. Codeflash reads config from pom.xml <properties> or gradle.properties, and auto-detects source/test roots from build tool conventions. Changes: - Add parse_java_project_config() to read codeflash.* properties from pom.xml and gradle.properties - Add multi-module Maven scanning: parses each module's pom.xml for <sourceDirectory> and <testSourceDirectory>, picks module with most Java files as source root, identifies test modules by name - Route Java projects through build-file detection in config_parser.py before falling back to pyproject.toml - Detect Java language from pom.xml/build.gradle presence (no config needed) - Fix project_root for multi-module projects (was resolving to sub-module) - Fix JFR parser / separators (JVM uses com/example, normalized to com.example) - Fix graceful timeout (SIGTERM before SIGKILL for JFR dump + shutdown hooks) - Remove isRecording() check from TracingTransformer (was preventing class instrumentation for classes loaded during serialization) - Delete all codeflash.toml files from fixtures and code_to_optimize - Add 33 config detection tests - Update docs for zero-config Java setup Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 59031a1 commit 3f95ff6

19 files changed

Lines changed: 1079 additions & 260 deletions

File tree

code_to_optimize/java-gradle/codeflash.toml

Lines changed: 0 additions & 4 deletions
This file was deleted.

code_to_optimize/java/codeflash.toml

Lines changed: 0 additions & 6 deletions
This file was deleted.

codeflash-java-runtime/src/main/java/com/codeflash/tracer/TracingTransformer.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,6 @@ public byte[] transform(ClassLoader loader, String className,
2222
return null;
2323
}
2424

25-
// Skip instrumentation if we're inside a recording call (e.g., during Kryo serialization)
26-
if (TraceRecorder.isRecording()) {
27-
return null;
28-
}
29-
3025
// Skip internal JDK, framework, and synthetic classes
3126
if (className.startsWith("java/")
3227
|| className.startsWith("javax/")

codeflash/cli_cmds/cli.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -185,11 +185,16 @@ def process_pyproject_config(args: Namespace) -> Namespace:
185185
args.ignore_paths = normalize_ignore_paths(args.ignore_paths, base_path=args.module_root)
186186
# If module-root is "." then all imports are relatives to it.
187187
# in this case, the ".." becomes outside project scope, causing issues with un-importable paths
188-
args.project_root = project_root_from_module_root(args.module_root, pyproject_file_path)
188+
if is_java_project and pyproject_file_path.is_dir():
189+
# For Java projects, pyproject_file_path IS the project root directory (not a file)
190+
args.project_root = pyproject_file_path.resolve()
191+
args.test_project_root = pyproject_file_path.resolve()
192+
else:
193+
args.project_root = project_root_from_module_root(args.module_root, pyproject_file_path)
194+
args.test_project_root = project_root_from_module_root(args.tests_root, pyproject_file_path)
189195
args.tests_root = Path(args.tests_root).resolve()
190196
if args.benchmarks_root:
191197
args.benchmarks_root = Path(args.benchmarks_root).resolve()
192-
args.test_project_root = project_root_from_module_root(args.tests_root, pyproject_file_path)
193198
if is_LSP_enabled():
194199
args.all = None
195200
return args
@@ -208,8 +213,6 @@ def project_root_from_module_root(module_root: Path, pyproject_file_path: Path)
208213
return current.resolve()
209214
if (current / "build.gradle").exists() or (current / "build.gradle.kts").exists():
210215
return current.resolve()
211-
if (current / "codeflash.toml").exists():
212-
return current.resolve()
213216
current = current.parent
214217

215218
return module_root.parent.resolve()
@@ -370,7 +373,7 @@ def _build_parser() -> ArgumentParser:
370373
subparsers.add_parser("vscode-install", help="Install the Codeflash VSCode extension")
371374
subparsers.add_parser("init-actions", help="Initialize GitHub Actions workflow")
372375

373-
trace_optimize = subparsers.add_parser("optimize", help="Trace and optimize your project.")
376+
trace_optimize = subparsers.add_parser("optimize", help="Trace and optimize your project.", add_help=False)
374377

375378
trace_optimize.add_argument(
376379
"--max-function-count",

codeflash/code_utils/config_parser.py

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,29 @@
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+
1536
def find_pyproject_toml(config_file: Path | None = None) -> Path:
16-
# Find the pyproject.toml or codeflash.toml file on the root of the project
37+
# Find the pyproject.toml file on the root of the project
1738

1839
if config_file is not None:
1940
config_file = Path(config_file)
@@ -29,21 +50,13 @@ def find_pyproject_toml(config_file: Path | None = None) -> Path:
2950
# see if it was encountered before in search
3051
if cur_path in PYPROJECT_TOML_CACHE:
3152
return PYPROJECT_TOML_CACHE[cur_path]
32-
# map current path to closest file - check both pyproject.toml and codeflash.toml
3353
while dir_path != dir_path.parent:
34-
# First check pyproject.toml (Python projects)
3554
config_file = dir_path / "pyproject.toml"
3655
if config_file.exists():
3756
PYPROJECT_TOML_CACHE[cur_path] = config_file
3857
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
4558
dir_path = dir_path.parent
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."
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."
4760

4861
raise ValueError(msg) from None
4962

@@ -90,33 +103,29 @@ def find_conftest_files(test_paths: list[Path]) -> list[Path]:
90103
return list(list_of_conftest_files)
91104

92105

93-
# TODO for claude: There should be different functions to parse it per language, which should be chosen during runtime
94106
def parse_config_file(
95107
config_file_path: Path | None = None, override_formatter_check: bool = False
96108
) -> tuple[dict[str, Any], Path]:
109+
# Java projects: read config from pom.xml/gradle.properties (no standalone config file needed)
110+
if config_file_path is None:
111+
java_config = _try_parse_java_build_config()
112+
if java_config is not None:
113+
config, project_root = java_config
114+
return config, project_root
115+
97116
package_json_path = find_package_json(config_file_path)
98117
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
100-
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
108118

109119
# When both config files exist, prefer the one closer to CWD.
110120
# This prevents a parent-directory package.json (e.g., monorepo root)
111-
# from overriding a closer pyproject.toml or codeflash.toml.
121+
# from overriding a closer pyproject.toml.
112122
use_package_json = False
113123
if package_json_path:
114-
if closest_toml_path is None:
124+
if pyproject_toml_path is None:
115125
use_package_json = True
116126
else:
117-
# Compare depth: more path parts = closer to CWD = more specific
118127
package_json_depth = len(package_json_path.parent.parts)
119-
toml_depth = len(closest_toml_path.parent.parts)
128+
toml_depth = len(pyproject_toml_path.parent.parts)
120129
use_package_json = package_json_depth >= toml_depth
121130

122131
if use_package_json:
@@ -160,7 +169,7 @@ def parse_config_file(
160169
if config == {} and lsp_mode:
161170
return {}, config_file_path
162171

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

codeflash/discovery/functions_to_optimize.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -554,11 +554,13 @@ def get_all_replay_test_functions(
554554

555555

556556
def _get_java_replay_test_functions(
557-
replay_test: list[Path], test_cfg: TestConfig, project_root_path: Path
557+
replay_test: list[Path], test_cfg: TestConfig, project_root_path: Path | str
558558
) -> tuple[dict[Path, list[FunctionToOptimize]], Path]:
559559
"""Parse Java replay test files to extract functions and trace file path."""
560560
from codeflash.languages.java.replay_test import parse_replay_test_metadata
561561

562+
project_root_path = Path(project_root_path)
563+
562564
trace_file_path: Path | None = None
563565
functions: dict[Path, list[FunctionToOptimize]] = defaultdict(list)
564566

0 commit comments

Comments
 (0)