Skip to content

3.9 Implementing the Compile Verification Module

Zhoumy303 edited this page Sep 27, 2026 · 1 revision

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.


Prerequisites

  • Completed Task 8: The project generator can generate a complete project including Java code, resource files, and textures.
  • The generated project's gradlew has executable permission (handled by render_project in Task 2).
  • JDK 25 is installed and ./gradlew runs normally from the command line.

Step 1: Create modsmith/verifier/gradle.py

This module provides two core functions:

  • run_gradle_build(project_dir: Path) -> BuildResult: runs ./gradlew build and 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_blueprint

Key notes:

  • run_gradle_build uses subprocess.run to execute gradlew build, with a 5-minute timeout.
  • _extract_errors extracts lines containing "错误:" or "error:" from stdout/stderr as feedback.
  • build_with_retry loops: generate project, compile, extract errors, call the LLM to correct the blueprint, until success or the maximum number of retries is reached.

Step 2: Update the generate_project signature in modsmith/generator/project.py

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}")

Step 3: Create the test script test_build.py

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.py

Expected 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.

Step 4: Integrate into the CLI (optional, preparing for Task 11)

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.


Step 5: Common issues and solutions

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.


Step 6: Task 9 Acceptance Criteria

  • modsmith/verifier/gradle.py is implemented, containing run_gradle_build and build_with_retry.
  • run_gradle_build can correctly execute ./gradlew build and return the result.
  • build_with_retry can call the LLM to correct the blueprint and retry when compilation fails.
  • test_build.py runs 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.

Clone this wiki locally