Skip to content

Commit cc6bffc

Browse files
authored
Merge branch 'main' into feat-staging
2 parents f5f1040 + 67a9e79 commit cc6bffc

File tree

11 files changed

+201
-61
lines changed

11 files changed

+201
-61
lines changed

codeflash/code_utils/shell_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
SHELL_RC_EXPORT_PATTERN = re.compile(r"^set CODEFLASH_API_KEY=(cf-.*)$", re.MULTILINE)
1616
SHELL_RC_EXPORT_PREFIX = "set CODEFLASH_API_KEY="
1717
else:
18-
SHELL_RC_EXPORT_PATTERN = re.compile(r'^export CODEFLASH_API_KEY="?(cf-[^\s"]+)"?$', re.MULTILINE)
18+
SHELL_RC_EXPORT_PATTERN = re.compile(r'^(?!#)export CODEFLASH_API_KEY=[\'"]?(cf-[^\s"]+)[\'"]$', re.MULTILINE)
1919
SHELL_RC_EXPORT_PREFIX = "export CODEFLASH_API_KEY="
2020

2121

codeflash/lsp/beta.py

Lines changed: 72 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -36,27 +36,71 @@ def get_optimizable_functions(
3636
server: CodeflashLanguageServer, params: OptimizableFunctionsParams
3737
) -> dict[str, list[str]]:
3838
file_path = Path(uris.to_fs_path(params.textDocument.uri))
39-
server.optimizer.args.file = file_path
40-
server.optimizer.args.previous_checkpoint_functions = False
41-
optimizable_funcs, _ = server.optimizer.get_optimizable_functions()
42-
path_to_qualified_names = {}
43-
for path, functions in optimizable_funcs.items():
44-
path_to_qualified_names[path.as_posix()] = [func.qualified_name for func in functions]
45-
return path_to_qualified_names
39+
server.show_message_log(f"Getting optimizable functions for: {file_path}", "Info")
40+
41+
# Save original args to restore later
42+
original_file = getattr(server.optimizer.args, "file", None)
43+
original_function = getattr(server.optimizer.args, "function", None)
44+
original_checkpoint = getattr(server.optimizer.args, "previous_checkpoint_functions", None)
45+
46+
server.show_message_log(f"Original args - file: {original_file}, function: {original_function}", "Info")
47+
48+
try:
49+
# Set temporary args for this request only
50+
server.optimizer.args.file = file_path
51+
server.optimizer.args.function = None # Always get ALL functions, not just one
52+
server.optimizer.args.previous_checkpoint_functions = False
53+
54+
server.show_message_log("Calling get_optimizable_functions...", "Info")
55+
optimizable_funcs, _ = server.optimizer.get_optimizable_functions()
56+
57+
path_to_qualified_names = {}
58+
for path, functions in optimizable_funcs.items():
59+
path_to_qualified_names[path.as_posix()] = [func.qualified_name for func in functions]
60+
61+
server.show_message_log(
62+
f"Found {len(path_to_qualified_names)} files with functions: {path_to_qualified_names}", "Info"
63+
)
64+
return path_to_qualified_names
65+
finally:
66+
# Restore original args to prevent state corruption
67+
if original_file is not None:
68+
server.optimizer.args.file = original_file
69+
if original_function is not None:
70+
server.optimizer.args.function = original_function
71+
else:
72+
server.optimizer.args.function = None
73+
if original_checkpoint is not None:
74+
server.optimizer.args.previous_checkpoint_functions = original_checkpoint
75+
76+
server.show_message_log(
77+
f"Restored args - file: {server.optimizer.args.file}, function: {server.optimizer.args.function}", "Info"
78+
)
4679

4780

4881
@server.feature("initializeFunctionOptimization")
4982
def initialize_function_optimization(
5083
server: CodeflashLanguageServer, params: FunctionOptimizationParams
5184
) -> dict[str, str]:
5285
file_path = Path(uris.to_fs_path(params.textDocument.uri))
86+
server.show_message_log(f"Initializing optimization for function: {params.functionName} in {file_path}", "Info")
87+
88+
# IMPORTANT: Store the specific function for optimization, but don't corrupt global state
5389
server.optimizer.args.function = params.functionName
5490
server.optimizer.args.file = file_path
91+
92+
server.show_message_log(
93+
f"Args set - function: {server.optimizer.args.function}, file: {server.optimizer.args.file}", "Info"
94+
)
95+
5596
optimizable_funcs, _ = server.optimizer.get_optimizable_functions()
5697
if not optimizable_funcs:
98+
server.show_message_log(f"No optimizable functions found for {params.functionName}", "Warning")
5799
return {"functionName": params.functionName, "status": "not found", "args": None}
100+
58101
fto = optimizable_funcs.popitem()[1][0]
59102
server.optimizer.current_function_being_optimized = fto
103+
server.show_message_log(f"Successfully initialized optimization for {params.functionName}", "Info")
60104
return {"functionName": params.functionName, "status": "success"}
61105

62106

@@ -136,11 +180,20 @@ def generate_tests(server: CodeflashLanguageServer, params: FunctionOptimization
136180

137181

138182
@server.feature("performFunctionOptimization")
139-
def perform_function_optimization(
183+
def perform_function_optimization( # noqa: PLR0911
140184
server: CodeflashLanguageServer, params: FunctionOptimizationParams
141185
) -> dict[str, str]:
186+
server.show_message_log(f"Starting optimization for function: {params.functionName}", "Info")
142187
current_function = server.optimizer.current_function_being_optimized
143188

189+
if not current_function:
190+
server.show_message_log(f"No current function being optimized for {params.functionName}", "Error")
191+
return {
192+
"functionName": params.functionName,
193+
"status": "error",
194+
"message": "No function currently being optimized",
195+
}
196+
144197
module_prep_result = server.optimizer.prepare_module_for_optimization(current_function.file_path)
145198

146199
validated_original_code, original_module_ast = module_prep_result
@@ -214,25 +267,28 @@ def perform_function_optimization(
214267
)
215268

216269
if not best_optimization:
270+
server.show_message_log(
271+
f"No best optimizations found for function {function_to_optimize_qualified_name}", "Warning"
272+
)
217273
return {
218274
"functionName": params.functionName,
219275
"status": "error",
220276
"message": f"No best optimizations found for function {function_to_optimize_qualified_name}",
221277
}
222278

223279
optimized_source = best_optimization.candidate.source_code
280+
speedup = original_code_baseline.runtime / best_optimization.runtime
281+
282+
server.show_message_log(f"Optimization completed for {params.functionName} with {speedup:.2f}x speedup", "Info")
283+
284+
# CRITICAL: Clear the function filter after optimization to prevent state corruption
285+
server.optimizer.args.function = None
286+
server.show_message_log("Cleared function filter to prevent state corruption", "Info")
224287

225288
return {
226289
"functionName": params.functionName,
227290
"status": "success",
228291
"message": "Optimization completed successfully",
229-
"extra": f"Speedup: {original_code_baseline.runtime / best_optimization.runtime:.2f}x faster",
292+
"extra": f"Speedup: {speedup:.2f}x faster",
230293
"optimization": optimized_source,
231294
}
232-
233-
234-
if __name__ == "__main__":
235-
from codeflash.cli_cmds.console import console
236-
237-
console.quiet = True
238-
server.start_io()

codeflash/lsp/server.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from pathlib import Path
44
from typing import TYPE_CHECKING, Any
55

6-
from lsprotocol.types import INITIALIZE
6+
from lsprotocol.types import INITIALIZE, MessageType, LogMessageParams
77
from pygls import uris
88
from pygls.protocol import LanguageServerProtocol, lsp_method
99
from pygls.server import LanguageServer
@@ -55,3 +55,24 @@ def initialize_optimizer(self, config_file: Path) -> None:
5555
args.no_pr = True # LSP server should not create PRs
5656
args = process_pyproject_config(args)
5757
self.optimizer = Optimizer(args)
58+
59+
def show_message_log(self, message: str, message_type: str) -> None:
60+
"""Send a log message to the client's output channel.
61+
62+
Args:
63+
message: The message to log
64+
message_type: String type - "Info", "Warning", "Error", or "Log"
65+
"""
66+
# Convert string message type to LSP MessageType enum
67+
type_mapping = {
68+
"Info": MessageType.Info,
69+
"Warning": MessageType.Warning,
70+
"Error": MessageType.Error,
71+
"Log": MessageType.Log
72+
}
73+
74+
lsp_message_type = type_mapping.get(message_type, MessageType.Info)
75+
76+
# Send log message to client (appears in output channel)
77+
log_params = LogMessageParams(type=lsp_message_type, message=message)
78+
self.lsp.notify("window/logMessage", log_params)

codeflash/lsp/server_entry.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
"""This script is the dedicated entry point for the Codeflash Language Server.
2+
It initializes the server and redirects its logs to stderr so that the
3+
VS Code client can display them in the output channel.
4+
5+
This script is run by the VS Code extension and is not intended to be
6+
executed directly by users.
7+
"""
8+
9+
import logging
10+
import sys
11+
12+
from codeflash.lsp.beta import server
13+
14+
15+
# Configure logging to stderr for VS Code output channel
16+
def setup_logging():
17+
# Clear any existing handlers to prevent conflicts
18+
root_logger = logging.getLogger()
19+
root_logger.handlers.clear()
20+
21+
# Set up stderr handler for VS Code output channel with [LSP-Server] prefix
22+
handler = logging.StreamHandler(sys.stderr)
23+
handler.setFormatter(logging.Formatter("[LSP-Server] %(asctime)s [%(levelname)s]: %(message)s"))
24+
25+
# Configure root logger
26+
root_logger.addHandler(handler)
27+
root_logger.setLevel(logging.INFO)
28+
29+
# Also configure the pygls logger specifically
30+
pygls_logger = logging.getLogger("pygls")
31+
pygls_logger.setLevel(logging.INFO)
32+
33+
return root_logger
34+
35+
36+
if __name__ == "__main__":
37+
# Set up logging
38+
log = setup_logging()
39+
log.info("Starting Codeflash Language Server...")
40+
41+
# Silence the console module to prevent stdout pollution
42+
from codeflash.cli_cmds.console import console
43+
44+
console.quiet = True
45+
# console.enable()
46+
47+
# Start the language server
48+
server.start_io()

codeflash/optimization/function_optimizer.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1492,11 +1492,9 @@ def submit_test_generation_tasks(
14921492
]
14931493

14941494
def cleanup_generated_files(self) -> None:
1495-
paths_to_cleanup = [self.test_cfg.concolic_test_root_dir]
1495+
paths_to_cleanup = []
14961496
for test_file in self.test_files:
14971497
paths_to_cleanup.append(test_file.instrumented_behavior_file_path)
14981498
paths_to_cleanup.append(test_file.benchmarking_file_path)
14991499

15001500
cleanup_paths(paths_to_cleanup)
1501-
if hasattr(get_run_tmp_file, "tmpdir"):
1502-
get_run_tmp_file.tmpdir.cleanup()

codeflash/optimization/optimizer.py

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from codeflash.api.aiservice import AiServiceClient, LocalAiServiceClient
1313
from codeflash.cli_cmds.console import console, logger, progress_bar
1414
from codeflash.code_utils import env_utils
15-
from codeflash.code_utils.code_utils import cleanup_paths
15+
from codeflash.code_utils.code_utils import cleanup_paths, get_run_tmp_file
1616
from codeflash.code_utils.env_utils import get_pr_number
1717
from codeflash.either import is_successful
1818
from codeflash.models.models import ValidCode
@@ -289,31 +289,36 @@ def run(self) -> None:
289289
f"{function_to_optimize.qualified_name}"
290290
)
291291
console.rule()
292+
function_optimizer = None
293+
try:
294+
function_optimizer = self.create_function_optimizer(
295+
function_to_optimize,
296+
function_to_tests=function_to_tests,
297+
function_to_optimize_source_code=validated_original_code[original_module_path].source_code,
298+
function_benchmark_timings=function_benchmark_timings,
299+
total_benchmark_timings=total_benchmark_timings,
300+
original_module_ast=original_module_ast,
301+
original_module_path=original_module_path,
302+
)
292303

293-
function_optimizer = self.create_function_optimizer(
294-
function_to_optimize,
295-
function_to_tests=function_to_tests,
296-
function_to_optimize_source_code=validated_original_code[original_module_path].source_code,
297-
function_benchmark_timings=function_benchmark_timings,
298-
total_benchmark_timings=total_benchmark_timings,
299-
original_module_ast=original_module_ast,
300-
original_module_path=original_module_path,
301-
)
302-
303-
self.current_function_optimizer = (
304-
function_optimizer # needed to clean up from the outside of this function
305-
)
306-
best_optimization = function_optimizer.optimize_function()
307-
if self.functions_checkpoint:
308-
self.functions_checkpoint.add_function_to_checkpoint(
309-
function_to_optimize.qualified_name_with_modules_from_root(self.args.project_root)
304+
self.current_function_optimizer = (
305+
function_optimizer # needed to clean up from the outside of this function
310306
)
311-
if is_successful(best_optimization):
312-
optimizations_found += 1
313-
else:
314-
logger.warning(best_optimization.failure())
315-
console.rule()
316-
continue
307+
best_optimization = function_optimizer.optimize_function()
308+
if self.functions_checkpoint:
309+
self.functions_checkpoint.add_function_to_checkpoint(
310+
function_to_optimize.qualified_name_with_modules_from_root(self.args.project_root)
311+
)
312+
if is_successful(best_optimization):
313+
optimizations_found += 1
314+
else:
315+
logger.warning(best_optimization.failure())
316+
console.rule()
317+
continue
318+
finally:
319+
if function_optimizer is not None:
320+
function_optimizer.cleanup_generated_files()
321+
317322
ph("cli-optimize-run-finished", {"optimizations_found": optimizations_found})
318323
if self.functions_checkpoint:
319324
self.functions_checkpoint.cleanup()
@@ -351,6 +356,9 @@ def cleanup_temporary_paths(self) -> None:
351356
if self.current_function_optimizer:
352357
self.current_function_optimizer.cleanup_generated_files()
353358

359+
if hasattr(get_run_tmp_file, "tmpdir"):
360+
get_run_tmp_file.tmpdir.cleanup()
361+
del get_run_tmp_file.tmpdir
354362
cleanup_paths([self.test_cfg.concolic_test_root_dir, self.replay_tests_dir])
355363

356364

codeflash/telemetry/posthog_cf.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from codeflash.api.cfapi import get_user_id
99
from codeflash.cli_cmds.console import logger
10-
from codeflash.version import __version__, __version_tuple__
10+
from codeflash.version import __version__
1111

1212
_posthog = None
1313

@@ -36,7 +36,7 @@ def ph(event: str, properties: dict[str, Any] | None = None) -> None:
3636
return
3737

3838
properties = properties or {}
39-
properties.update({"cli_version": __version__, "cli_version_tuple": __version_tuple__})
39+
properties.update({"cli_version": __version__})
4040

4141
user_id = get_user_id()
4242

codeflash/update_license_version.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22
from datetime import datetime
33
from pathlib import Path
44

5-
from version import __version_tuple__
5+
from .version import __version__
66

77

88
def main() -> None:
99
# Use the version tuple from version.py
10-
version = __version_tuple__
10+
version = __version__
1111

1212
# Use the major and minor version components from the version tuple
13-
major_minor_version = ".".join(map(str, version[:2]))
13+
major_minor_version = ".".join(map(str, version.split(".")[:2]))
1414

1515
# Define the pattern to search for and the replacement string for the version
1616
version_pattern = re.compile(r"(Licensed Work:\s+Codeflash Client version\s+)(0\.\d+)(\.x)")

codeflash/version.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
# These version placeholders will be replaced by uv-dynamic-versioning during build.
2-
__version__ = "0.14.6"
3-
__version_tuple__ = (0, 14, 6)
2+
__version__ = "0.14.7"

pyproject.toml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -268,17 +268,17 @@ enable = true
268268
style = "pep440"
269269
vcs = "git"
270270

271-
[tool.uv-dynamic-versioning.substitution]
272-
files = ["codeflash/version.py"]
273-
274-
[tool.uv-dynamic-versioning.files."codeflash/version.py"]
275-
persistent-substitution = true
276-
initial-content = """
277-
# These version placeholders will be replaced by uv-dynamic-versioning during build.
278-
__version__ = "0.0.0"
279-
__version_tuple__ = (0, 0, 0)
271+
[tool.hatch.build.hooks.version]
272+
path = "codeflash/version.py"
273+
template = """# These version placeholders will be replaced by uv-dynamic-versioning during build.
274+
__version__ = "{version}"
280275
"""
281276

277+
278+
#[tool.hatch.build.hooks.custom]
279+
#path = "codeflash/update_license_version.py"
280+
281+
282282
[tool.codeflash]
283283
module-root = "codeflash"
284284
tests-root = "tests"

0 commit comments

Comments
 (0)