-
Notifications
You must be signed in to change notification settings - Fork 4.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
refactor: fixed broken swe-bench docker image generation. #1262
Changes from all commits
c2faf5d
26e0075
7480c26
edd51bc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,4 +1,6 @@ | ||||||||||||||||||||||||||||||
import signal | ||||||||||||||||||||||||||||||
import subprocess | ||||||||||||||||||||||||||||||
import threading | ||||||||||||||||||||||||||||||
import typing as t | ||||||||||||||||||||||||||||||
from concurrent.futures import ThreadPoolExecutor | ||||||||||||||||||||||||||||||
from pathlib import Path | ||||||||||||||||||||||||||||||
|
@@ -9,7 +11,89 @@ | |||||||||||||||||||||||||||||
logs = Path.cwd() / "logs" | ||||||||||||||||||||||||||||||
logs.mkdir(exist_ok=True) | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
errors = [] | ||||||||||||||||||||||||||||||
# Lock for synchronizing access to the success log | ||||||||||||||||||||||||||||||
success_lock = threading.Lock() | ||||||||||||||||||||||||||||||
# Global set to keep track of successfully pushed images | ||||||||||||||||||||||||||||||
successful_builds = set() | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
# Load successful builds from logs/success.log into the global set | ||||||||||||||||||||||||||||||
def load_successful_builds() -> None: | ||||||||||||||||||||||||||||||
success_file = logs / "success.log" | ||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider using constants for frequently used strings and paths: SUCCESS_LOG_FILE = "success.log"
DOCKER_IMAGE_PREFIX = "composio/swe"
# Then use them like:
success_file = logs / SUCCESS_LOG_FILE
full_tag = f"{DOCKER_IMAGE_PREFIX}:{tag_part}" This makes the code more maintainable and reduces the risk of typos. |
||||||||||||||||||||||||||||||
if success_file.exists(): | ||||||||||||||||||||||||||||||
with open(success_file, "r", encoding="utf-8") as f: | ||||||||||||||||||||||||||||||
for line in f: | ||||||||||||||||||||||||||||||
tag = line.strip() | ||||||||||||||||||||||||||||||
if tag: | ||||||||||||||||||||||||||||||
successful_builds.add(tag) | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
# Record a successful build by writing to the log file and updating the global set | ||||||||||||||||||||||||||||||
def record_success(tag: str) -> None: | ||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider adding docstrings to the new functions for better code documentation: def record_success(tag: str) -> None:
"""Record a successful build by writing to the success log file and updating the global set.
Args:
tag: The docker image tag that was successfully built
Thread-safe: Uses success_lock for synchronization
"""
with success_lock:
successful_builds.add(tag)
with open(logs / "success.log", "a", encoding="utf-8") as f:
f.write(tag + "\n") |
||||||||||||||||||||||||||||||
with success_lock: | ||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider validating the docker tag format before recording success to prevent invalid entries: def is_valid_docker_tag(tag: str) -> bool:
"""Validate docker tag format according to Docker's rules."""
# Basic validation - can be expanded based on needs
return bool(re.match(r'^[a-z0-9][a-z0-9._-]*$', tag))
def record_success(tag: str) -> None:
if not is_valid_docker_tag(tag):
logger.warning(f"Invalid docker tag format: {tag}")
return
with success_lock:
successful_builds.add(tag)
with open(logs / "success.log", "a", encoding="utf-8") as f:
f.write(tag + "\n") |
||||||||||||||||||||||||||||||
successful_builds.add(tag) | ||||||||||||||||||||||||||||||
with open(logs / "success.log", "a", encoding="utf-8") as f: | ||||||||||||||||||||||||||||||
f.write(tag + "\n") | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
# Insert new global variables and functions for graceful stop and resume support | ||||||||||||||||||||||||||||||
stop_requested = False | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
def handle_stop(signum, frame): | ||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider adding proper logging instead of print statements for better observability and debugging: def handle_stop(signum, frame):
global stop_requested
logger.info("Received stop signal. Gracefully stopping new builds...")
stop_requested = True |
||||||||||||||||||||||||||||||
global stop_requested | ||||||||||||||||||||||||||||||
print("Received stop signal. Gracefully stopping new builds...") | ||||||||||||||||||||||||||||||
stop_requested = True | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
def _base(generated: Path, multi: bool = False) -> None: | ||||||||||||||||||||||||||||||
base = generated / "base" | ||||||||||||||||||||||||||||||
with ThreadPoolExecutor() as executor: | ||||||||||||||||||||||||||||||
futures = [] | ||||||||||||||||||||||||||||||
for file in base.iterdir(): | ||||||||||||||||||||||||||||||
if stop_requested: | ||||||||||||||||||||||||||||||
print("Graceful stop activated. Halting base builds.") | ||||||||||||||||||||||||||||||
break | ||||||||||||||||||||||||||||||
try: | ||||||||||||||||||||||||||||||
_, tag_part = file.name.split(".", maxsplit=1) | ||||||||||||||||||||||||||||||
except ValueError: | ||||||||||||||||||||||||||||||
print(f"Skipping invalid file name format: {file.name}") | ||||||||||||||||||||||||||||||
continue | ||||||||||||||||||||||||||||||
full_tag = f"composio/swe:{tag_part}" | ||||||||||||||||||||||||||||||
if full_tag in successful_builds: | ||||||||||||||||||||||||||||||
print(f"Skipping build for {full_tag} as it has been already pushed.") | ||||||||||||||||||||||||||||||
continue | ||||||||||||||||||||||||||||||
futures.append(executor.submit(_build, file, tag_part, multi)) | ||||||||||||||||||||||||||||||
[fut.result() for fut in futures] | ||||||||||||||||||||||||||||||
Comment on lines
+66
to
+67
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The 📝 Committable Code Suggestion
Suggested change
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
def _swes(generated: Path, multi: bool = False) -> None: | ||||||||||||||||||||||||||||||
with ThreadPoolExecutor() as executor: | ||||||||||||||||||||||||||||||
futures = [] | ||||||||||||||||||||||||||||||
for child in generated.iterdir(): | ||||||||||||||||||||||||||||||
if child.name == "base": | ||||||||||||||||||||||||||||||
continue | ||||||||||||||||||||||||||||||
if child.is_file(): | ||||||||||||||||||||||||||||||
continue | ||||||||||||||||||||||||||||||
repo = child.name.replace("__", "-") | ||||||||||||||||||||||||||||||
for version in child.iterdir(): | ||||||||||||||||||||||||||||||
if stop_requested: | ||||||||||||||||||||||||||||||
print("Graceful stop activated. Halting SWES builds.") | ||||||||||||||||||||||||||||||
break | ||||||||||||||||||||||||||||||
tag_part = f"{repo}-{version.name.replace('.', '-') }" | ||||||||||||||||||||||||||||||
full_tag = f"composio/swe:{tag_part}" | ||||||||||||||||||||||||||||||
if full_tag in successful_builds: | ||||||||||||||||||||||||||||||
print( | ||||||||||||||||||||||||||||||
f"Skipping build for {full_tag} as it has been already pushed." | ||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||
continue | ||||||||||||||||||||||||||||||
futures.append( | ||||||||||||||||||||||||||||||
executor.submit(_build, version / "Dockerfile", tag_part, multi) | ||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||
if stop_requested: | ||||||||||||||||||||||||||||||
break | ||||||||||||||||||||||||||||||
[fut.result() for fut in futures] | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
ARCHS = ("linux/arm64", "linux/amd64") | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
|
@@ -50,81 +134,44 @@ def _build(file: Path, tag: str, multi: bool, *flags: str) -> None: | |||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
if process.returncode == 0: | ||||||||||||||||||||||||||||||
print(f"Finished build for {tag}") | ||||||||||||||||||||||||||||||
record_success(tag) | ||||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||||
print(f"Error building {tag} - {logs / log}") | ||||||||||||||||||||||||||||||
Comment on lines
134
to
139
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing error handling for failed builds - successful builds are tracked but failures are silently ignored. Should maintain error state and handle failed builds appropriately. 📝 Committable Code Suggestion
Suggested change
|
||||||||||||||||||||||||||||||
errors.append(f"Error building {tag} - {logs / log}") | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
def _base(generated: Path, multi: bool = False) -> None: | ||||||||||||||||||||||||||||||
base = generated / "base" | ||||||||||||||||||||||||||||||
with ThreadPoolExecutor() as executor: | ||||||||||||||||||||||||||||||
futures = [] | ||||||||||||||||||||||||||||||
for file in base.iterdir(): | ||||||||||||||||||||||||||||||
_, tag = file.name.split(".", maxsplit=1) | ||||||||||||||||||||||||||||||
futures.append(executor.submit(_build, file, tag, multi)) | ||||||||||||||||||||||||||||||
_ = [fut.result() for fut in futures] | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
def _swes(generated: Path, multi: bool = False) -> None: | ||||||||||||||||||||||||||||||
with ThreadPoolExecutor() as executor: | ||||||||||||||||||||||||||||||
futures = [] | ||||||||||||||||||||||||||||||
for child in generated.iterdir(): | ||||||||||||||||||||||||||||||
if child.name == "base": | ||||||||||||||||||||||||||||||
continue | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
if child.is_file(): | ||||||||||||||||||||||||||||||
continue | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
repo = child.name.replace("__", "-") | ||||||||||||||||||||||||||||||
for version in child.iterdir(): | ||||||||||||||||||||||||||||||
tag = f"{repo}-{version.name.replace('.', '-')}" | ||||||||||||||||||||||||||||||
futures.append( | ||||||||||||||||||||||||||||||
executor.submit( | ||||||||||||||||||||||||||||||
_build, | ||||||||||||||||||||||||||||||
version / "Dockerfile", | ||||||||||||||||||||||||||||||
tag, | ||||||||||||||||||||||||||||||
multi, | ||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
_ = [fut.result() for fut in futures] | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
def _pyenv(file: t.Optional[Path] = None, multi: bool = False) -> None: | ||||||||||||||||||||||||||||||
print("Print building pyenv base") | ||||||||||||||||||||||||||||||
file = file or Path(__file__).parent / "templates" / "Dockerfile.pyenv" | ||||||||||||||||||||||||||||||
full_tag = "composio/swe:pyenv" | ||||||||||||||||||||||||||||||
if full_tag in successful_builds: | ||||||||||||||||||||||||||||||
print(f"Skipping build for {full_tag} as it has already been pushed.") | ||||||||||||||||||||||||||||||
return | ||||||||||||||||||||||||||||||
_build(file=file, tag="pyenv", multi=multi) | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
@click.command(name="build") | ||||||||||||||||||||||||||||||
@click.argument( | ||||||||||||||||||||||||||||||
"generated", | ||||||||||||||||||||||||||||||
type=str, | ||||||||||||||||||||||||||||||
default="./generated", | ||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||
@click.argument("generated", type=str, default="./generated") | ||||||||||||||||||||||||||||||
@click.option( | ||||||||||||||||||||||||||||||
"--multi", | ||||||||||||||||||||||||||||||
is_flag=True, | ||||||||||||||||||||||||||||||
help="Use this flag to build multi-plaform images", | ||||||||||||||||||||||||||||||
"--multi", is_flag=True, help="Use this flag to build multi-plaform images" | ||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||
def build(generated: Path, multi: bool = False) -> None: | ||||||||||||||||||||||||||||||
"""Build docker images for SWEKIT.""" | ||||||||||||||||||||||||||||||
load_successful_builds() | ||||||||||||||||||||||||||||||
signal.signal(signal.SIGINT, handle_stop) | ||||||||||||||||||||||||||||||
signal.signal(signal.SIGTERM, handle_stop) | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
_pyenv(multi=multi) | ||||||||||||||||||||||||||||||
if len(errors) > 0: | ||||||||||||||||||||||||||||||
print("==== Errors ====") | ||||||||||||||||||||||||||||||
print("\n".join(errors)) | ||||||||||||||||||||||||||||||
return | ||||||||||||||||||||||||||||||
print("==== Successful Builds (after pyenv) ====") | ||||||||||||||||||||||||||||||
print("\n".join(successful_builds)) | ||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider sorting the 'successful_builds' set before printing for reproducible output. |
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
generated = Path(generated or Path.cwd() / "generated").resolve() | ||||||||||||||||||||||||||||||
_base(generated=generated, multi=multi) | ||||||||||||||||||||||||||||||
if len(errors) > 0: | ||||||||||||||||||||||||||||||
print("==== Errors ====") | ||||||||||||||||||||||||||||||
print("\n".join(errors)) | ||||||||||||||||||||||||||||||
return | ||||||||||||||||||||||||||||||
print("==== Successful Builds (after base) ====") | ||||||||||||||||||||||||||||||
print("\n".join(successful_builds)) | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
_swes(generated=generated, multi=multi) | ||||||||||||||||||||||||||||||
print("==== Errors ====") | ||||||||||||||||||||||||||||||
print("\n".join(errors)) | ||||||||||||||||||||||||||||||
print("==== Final Successful Builds ====") | ||||||||||||||||||||||||||||||
print("\n".join(successful_builds)) | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
if __name__ == "__main__": | ||||||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -5,13 +5,14 @@ | |||||||||||||
from pathlib import Path | ||||||||||||||
|
||||||||||||||
import click | ||||||||||||||
from swebench import get_eval_refs | ||||||||||||||
from swebench.harness.constants import SWEbenchInstance | ||||||||||||||
from swebench.harness.utils import load_swebench_dataset | ||||||||||||||
|
||||||||||||||
from composio import Action, ComposioToolSet | ||||||||||||||
from composio.utils.logging import WithLogger | ||||||||||||||
|
||||||||||||||
|
||||||||||||||
def group_task_instances(task_instances): | ||||||||||||||
def group_task_instances(task_instances: list[SWEbenchInstance]): | ||||||||||||||
groups = {} | ||||||||||||||
for instance in task_instances: | ||||||||||||||
repo = instance["repo"] | ||||||||||||||
|
@@ -43,11 +44,25 @@ def __init__( | |||||||||||||
self.outdir.mkdir() | ||||||||||||||
|
||||||||||||||
def generate(self): | ||||||||||||||
task_instances = get_eval_refs(data_path_or_name=self.dataset) | ||||||||||||||
task_instance_groups = group_task_instances(task_instances.values()) | ||||||||||||||
task_instances = load_swebench_dataset(name=self.dataset) | ||||||||||||||
task_instance_groups = group_task_instances(task_instances) | ||||||||||||||
for repo, versions in task_instance_groups.items(): | ||||||||||||||
self.logger.info(f"Repo {repo} with {set(versions.keys())} versions") | ||||||||||||||
for version, instances in versions.items(): | ||||||||||||||
outname = _repo_name(repo) | ||||||||||||||
docker_outdir = Path("generated") / outname / version | ||||||||||||||
|
||||||||||||||
# Check if files in generated directory are complete | ||||||||||||||
if ( | ||||||||||||||
docker_outdir.exists() | ||||||||||||||
and (docker_outdir / "deeplake").exists() | ||||||||||||||
and (docker_outdir / "fqdn_cache.json").exists() | ||||||||||||||
): | ||||||||||||||
self.logger.info( | ||||||||||||||
f"Skipping {repo} {version} - files already exist in generated directory" | ||||||||||||||
) | ||||||||||||||
continue | ||||||||||||||
|
||||||||||||||
self.logger.info(f"\tGenerating for version - {version}") | ||||||||||||||
self.create_index( | ||||||||||||||
repository=repo, version=version, setup_ref_instance=instances[0] | ||||||||||||||
|
@@ -58,8 +73,8 @@ def create_index( | |||||||||||||
): | ||||||||||||||
outname = _repo_name(repository) | ||||||||||||||
outdir = self.outdir / outname / version | ||||||||||||||
if outdir.exists(): | ||||||||||||||
return | ||||||||||||||
docker_outdir = Path("generated") / outname / version | ||||||||||||||
|
||||||||||||||
repo_url = f"https://github.com/{repository}.git" | ||||||||||||||
base_commit = setup_ref_instance["base_commit"] | ||||||||||||||
if not (outdir / outname).exists(): | ||||||||||||||
|
@@ -76,10 +91,16 @@ def create_index( | |||||||||||||
["git", "checkout", base_commit], cwd=outdir / outname, check=True | ||||||||||||||
) | ||||||||||||||
|
||||||||||||||
composio_toolset = ComposioToolSet() | ||||||||||||||
composio_toolset = ComposioToolSet( | ||||||||||||||
metadata={ | ||||||||||||||
Action.CODE_ANALYSIS_TOOL_CREATE_CODE_MAP: { | ||||||||||||||
"dir_to_index_path": str(outdir / outname), | ||||||||||||||
}, | ||||||||||||||
}, | ||||||||||||||
) | ||||||||||||||
composio_toolset.execute_action( | ||||||||||||||
action=Action.CODE_ANALYSIS_TOOL_CREATE_CODE_MAP, | ||||||||||||||
params={"dir_to_index_path": str(outdir / outname)}, | ||||||||||||||
params={}, | ||||||||||||||
) | ||||||||||||||
with open(f"{Path.home()}/.composio/tmp/{outname}/fqdn_cache.json") as f: | ||||||||||||||
fqdn_index = json.load(f) | ||||||||||||||
|
@@ -91,16 +112,13 @@ def create_index( | |||||||||||||
) | ||||||||||||||
fqdn_index[k] = v | ||||||||||||||
|
||||||||||||||
docker_outdir = Path("generated") / outname / version | ||||||||||||||
# docker_outdir.mkdir(exist_ok=True, parents=True) | ||||||||||||||
with open( | ||||||||||||||
docker_outdir / "fqdn_cache.json", | ||||||||||||||
"w", | ||||||||||||||
) as f: | ||||||||||||||
json.dump(fqdn_index, f, indent=4) | ||||||||||||||
|
||||||||||||||
DEEPLAKE_PATH = docker_outdir / "deeplake" | ||||||||||||||
# DEEPLAKE_PATH.mkdir(exist_ok=True, parents=True) | ||||||||||||||
if not DEEPLAKE_PATH.exists(): | ||||||||||||||
shutil.copytree( | ||||||||||||||
f"{Path.home()}/.composio/tmp/{outname}/deeplake", | ||||||||||||||
Comment on lines
122
to
124
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Race condition when checking and creating directories. Use 📝 Committable Code Suggestion
Suggested change
|
||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider adding error handling for file operations in load_successful_builds() to handle potential IO errors gracefully: