-
Notifications
You must be signed in to change notification settings - Fork 0
3.9 Implementing the Compile Verification Module
The goal of this task is to automatically run ./gradlew build to compile the generated project. If compilation fails, feed the error log back to the LLM so it can correct the blueprint and regenerate, retrying at most 3 times, forming a "generate → compile → correct" loop.
After completing this task, your ModSmith will have automatic error-correction capability, greatly improving the compilability of generated projects.
- Completed Task 8: The project generator can generate a complete project including Java code, resource files, and textures.
- The generated project's
gradlewhas executable permission (handled byrender_projectin Task 2). - JDK 25 is installed and
./gradlewruns normally from the command line.
This module provides two core functions:
-
run_gradle_build(project_dir: Path) -> BuildResult: runs./gradlew buildand returns the result. -
build_with_retry(blueprint: dict, output_dir: Path, max_retries: int = 3) -> tuple[bool, dict]: generates the project and compiles it; on failure, calls the LLM to correct the blueprint and retries.
Write the following complete code:
"""Compile verification module: run Gradle build and feed failures back to the LLM to correct the blueprint."""
import os
import subprocess
from dataclasses import dataclass
from pathlib import Path
from modsmith.generator.project import generate_project
from modsmith.llm.client import generate_blueprint
from modsmith.blueprint.validator import validate_blueprint
@dataclass
class BuildResult:
"""Gradle build result."""
success: bool
stdout: str
stderr: str
returncode: int
def run_gradle_build(project_dir: Path) -> BuildResult:
"""Run ./gradlew build in the specified project directory."""
# Key: convert to absolute path to avoid cwd and path concatenation conflicts
project_dir = project_dir.resolve()
gradlew = project_dir / ("gradlew.bat" if os.name == "nt" else "gradlew")
if not gradlew.exists():
return BuildResult(
success=False,
stdout="",
stderr=f"Could not find {gradlew}",
returncode=-1,
)
try:
result = subprocess.run(
[str(gradlew), "build"],
cwd=str(project_dir),
capture_output=True,
text=True,
timeout=300,
)
return BuildResult(
success=(result.returncode == 0),
stdout=result.stdout,
stderr=result.stderr,
returncode=result.returncode,
)
except subprocess.TimeoutExpired as e:
return BuildResult(
success=False,
stdout=e.stdout or "",
stderr=f"Build timed out: {e}",
returncode=-1,
)
except Exception as e:
return BuildResult(
success=False,
stdout="",
stderr=f"Exception while running gradlew: {e}",
returncode=-1,
)
def _extract_errors(build_result: BuildResult) -> str:
"""Extract key error messages from the build result for feedback to the LLM."""
lines = []
# Extract error lines from stderr
for line in build_result.stderr.splitlines():
if "错误:" in line or "error:" in line.lower():
lines.append(line)
# If not found in stderr, try stdout
if not lines:
for line in build_result.stdout.splitlines():
if "错误:" in line or "error:" in line.lower():
lines.append(line)
if not lines:
# Fallback: return the last 20 lines
all_lines = (build_result.stderr + "\n" + build_result.stdout).splitlines()
lines = all_lines[-20:]
return "\n".join(lines)
def build_with_retry(
blueprint: dict,
output_dir: Path,
max_retries: int = 3,
) -> tuple[bool, dict]:
"""Generate the project and compile it; on failure, feed back to the LLM to correct the blueprint and retry.
Args:
blueprint: Initial blueprint dictionary.
output_dir: Output project directory.
max_retries: Maximum number of retries.
Returns:
(success, final blueprint dictionary)
"""
current_blueprint = blueprint
for attempt in range(1, max_retries + 1):
print(f"\n🔄 Attempt {attempt}: generating and compiling...")
# 1. Generate project
generate_project(current_blueprint, output_dir)
# 2. Compile
result = run_gradle_build(output_dir)
if result.success:
print(f"✅ Attempt {attempt}: compilation succeeded!")
return True, current_blueprint
# 3. Compilation failed; extract error information
error_summary = _extract_errors(result)
print(f"⚠️ Attempt {attempt}: compilation failed:\n{error_summary}")
# 4. Feed back to the LLM to correct the blueprint
correction_prompt = f"""The previously generated blueprint caused compilation to fail. The error information is as follows:
{error_summary}
Please correct the blueprint according to the above errors. Note:
- If the error is related to Java code, check whether the item type and related fields are complete.
- If the error is related to model or texture paths, check whether the item ID naming conforms to the conventions.
- Output only the corrected full blueprint JSON, without any explanation.
"""
try:
current_blueprint = generate_blueprint(correction_prompt)
validate_blueprint(current_blueprint)
except Exception as e:
print(f"⚠️ Error while correcting blueprint: {e}")
# Continue to the next loop, but it may still fail
print(f"❌ After {max_retries} attempts, compilation still failed.")
return False, current_blueprintKey notes:
-
run_gradle_buildusessubprocess.runto executegradlew build, with a 5-minute timeout. -
_extract_errorsextracts lines containing "错误:" or "error:" from stdout/stderr as feedback. -
build_with_retryloops: generate project, compile, extract errors, call the LLM to correct the blueprint, until success or the maximum number of retries is reached.
build_with_retry calls generate_project(current_blueprint, output_dir), so generate_project must accept both blueprint and output_dir. Confirm that generate_project in project.py is defined correctly:
def generate_project(blueprint: dict, output_dir: Path) -> None:
render_project(blueprint, output_dir)
generate_java_items(blueprint, output_dir)
generate_resources(blueprint, output_dir)
generate_all_textures(blueprint, output_dir)
print(f"✅ Project fully generated at: {output_dir}")Create at the project root:
"""Test the compile verification module."""
from pathlib import Path
from modsmith.blueprint.validator import generate_validated_blueprint
from modsmith.verifier.gradle import build_with_retry
# Generate the initial blueprint
blueprint = generate_validated_blueprint("Create an apple that restores 4 hunger points when eaten")
# Output directory
output_dir = Path("./build_test_project")
# Run compile verification
success, final_blueprint = build_with_retry(blueprint, output_dir)
if success:
print("🎉 Compile verification succeeded!")
print(f"Final blueprint: {final_blueprint}")
else:
print("❌ Compile verification failed.")Run:
python test_build.pyExpected result:
- If the project generated from the initial blueprint compiles, it prints
✅ Attempt 1: compilation succeeded!. - If compilation fails, it shows the error summary, then the LLM corrects the blueprint and retries, until success or the maximum number of attempts is reached.
In modsmith/cli.py, the generate command should eventually call build_with_retry. You can replace the placeholder logic in the CLI with a real call first, but a more complete integration is left to Task 11.
For now, do not modify the CLI; focus on the verification module itself.
| Problem | Cause | Solution |
|---|---|---|
FileNotFoundError: gradlew |
Project not generated or wrong path | Confirm generate_project ran successfully and output_dir is correct |
PermissionError |
gradlew has no execute permission |
Handled in Task 2; if still no permission, manually chmod +x gradlew
|
| Build timed out | First dependency download too slow | Configure a domestic Gradle mirror (see earlier plan), or increase timeout
|
| Still fails after LLM correction | Error message not specific enough | Enhance _extract_errors to extract more context, or increase retry count |
subprocess.os.name error |
Should be os.name
|
Fix to import os; os.name, see the correction below |
Note: The code uses subprocess.os.name, which is incorrect. It should be changed to import os and then os.name. Fix this line in run_gradle_build:
import os
# ...
gradlew = project_dir / ("gradlew.bat" if os.name == "nt" else "gradlew")Be sure to add import os at the top of gradle.py.
-
modsmith/verifier/gradle.pyis implemented, containingrun_gradle_buildandbuild_with_retry. -
run_gradle_buildcan correctly execute./gradlew buildand return the result. -
build_with_retrycan call the LLM to correct the blueprint and retry when compilation fails. -
test_build.pyruns end to end and finally outputs compilation success or failure. - When compilation succeeds, the generated project has a jar file under
build/libs/.
After completing this task, your ModSmith will have automatic compile verification and error-correction capability. Next, Task 10 will implement packaging output (zip/jar), Task 11 will refine the CLI entrypoint, and Task 12 will perform end-to-end testing and documentation.