Skip to content

3.10 Implementing Packaging Output

Zhoumy303 edited this page Oct 3, 2026 · 2 revisions

The goal of this task is to after successful compilation, package the generated project directory into a zip file, and copy the compilation artifact (jar) to the output directory for the user to download or test in-game.
After completing this task, ModSmith will be able to output a jar file that can be directly installed into the mods/ folder, or a complete project zip that can be further developed.


Prerequisites

  • Completed Task 9: build_with_retry() can automatically compile and correct the blueprint.
  • The generated project already has a compiled jar under build/libs/.
  • Python standard libraries shutil and zipfile are installed (no extra installation needed).

Step 1: Understand the contents of the packaging output

A complete ModSmith output should include:

Content Description
Project zip Complete project directory for the user to continue development in an IDE
jar file Compilation artifact, can be placed directly into the mods/ folder for in-game testing
Blueprint JSON Records the blueprint of this generation for reproducibility or modification
README Brief instructions on how to install and use

Packaging strategy:

  • Project zip is output under output_dir/, named <mod_id>-src.zip.
  • The jar file is copied from <project_dir>/build/libs/ to output_dir/.
  • The blueprint JSON is saved as <mod_id>-blueprint.json.
  • A short README.md is generated automatically.

Step 2: Create modsmith/packager/__init__.py

"""packager"""

Step 3: Create modsmith/packager/archive.py

"""Package project output: generate zip, copy jar, save blueprint."""

import json
import shutil
import zipfile
from pathlib import Path


def _write_readme(output_dir: Path, blueprint: dict) -> None:
    """Generate README.md explaining how to install and use the generated mod."""
    mod_id = blueprint["mod_id"]
    mod_name = blueprint.get("mod_name", mod_id)
    content = f"""# {mod_name}

A Fabric mod automatically generated by ModSmith.

## Installation

1. Install Fabric Loader for the corresponding Minecraft version.
2. Download [Fabric API](https://modrinth.com/mod/fabric-api) and place it in the `mods/` folder.
3. Place `{mod_id}-*.jar` into the `mods/` folder.
4. Launch Minecraft (Fabric profile).

## Development

If you want to continue development, unzip `{mod_id}-src.zip`, open it with IntelliJ IDEA, and run `./gradlew build`.

## Blueprint

This mod was generated from the following blueprint:

```json
{json.dumps(blueprint, indent=2, ensure_ascii=False)}
"""
    (output_dir / "README.md").write_text(content, encoding="utf-8")

def _zip_project(project_dir: Path, zip_path: Path) -> None:
    """Package the project directory into a zip file (excluding build directory and .gradle cache)."""
    EXCLUDE_DIRS = {"build", ".gradle", ".idea", "run"}
    with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
        for path in project_dir.rglob("*"):
            if path.is_dir():
                continue

            # Skip contents in excluded directories

            rel_parts = path.relative_to(project_dir).parts
            if any(part in EXCLUDE_DIRS for part in rel_parts):
                continue
            zf.write(path, path.relative_to(project_dir))


def _find_jar(project_dir: Path) -> Path | None:
    """Find the compiled jar under build/libs/ (excluding sources jar)."""
    libs_dir = project_dir / "build" / "libs"
    if not libs_dir.exists():
        return None
    jars = [
        p for p in libs_dir.glob("*.jar")
        if not p.name.endswith("-sources.jar") and not p.name.endswith("-dev.jar")
    ]
    if not jars:
        return None
    # Return the most recent one
    return max(jars, key=lambda p: p.stat().st_mtime)


def package_output(
    blueprint: dict,
    project_dir: Path,
    output_dir: Path,
) -> dict:
    """Package project output: zip, jar, blueprint, README.
    Args:
        blueprint: Blueprint dictionary.
        project_dir: Generated project directory.
        output_dir: Packaging output directory.

    Returns:
        A dictionary containing the paths of packaging results.
    """
    output_dir.mkdir(parents=True, exist_ok=True)
    mod_id = blueprint["mod_id"]
    results = {}

    # 1. Package source zip
    src_zip = output_dir / f"{mod_id}-src.zip"
    _zip_project(project_dir, src_zip)
    results["source_zip"] = src_zip
    print(f"📦 Source zip generated: {src_zip}")

    # 2. Copy compilation artifact jar
    jar = _find_jar(project_dir)
    if jar:
        dest_jar = output_dir / jar.name
        shutil.copy2(jar, dest_jar)
        results["jar"] = dest_jar
        print(f"📦 Compilation artifact copied: {dest_jar}")
    else:
        print("⚠️ Compiled jar not found. Please confirm compilation succeeded.")

    # 3. Save blueprint JSON
    blueprint_path = output_dir / f"{mod_id}-blueprint.json"
    blueprint_path.write_text(
        json.dumps(blueprint, indent=2, ensure_ascii=False),
        encoding="utf-8",
    )
    results["blueprint"] = blueprint_path
    print(f"📦 Blueprint saved: {blueprint_path}")

    # 4. Generate README
    _write_readme(output_dir, blueprint)
    results["readme"] = output_dir / "README.md"
    print(f"📦 README generated: {results['readme']}")

    return results    

Key notes:

  • _zip_project packages the source, excluding build/, .gradle/, .idea/, and other irrelevant directories.
  • _find_jar looks for a non-sources, non-dev jar under build/libs/.
  • _write_readme generates installation instructions, including the complete blueprint JSON.

Step 4: Integrate packaging into the CLI flow (preparing for Task 11)

Create a new file modsmith/pipeline.py:

"""End-to-end pipeline: from natural language to a packaged mod."""

from pathlib import Path

from modsmith.blueprint.validator import generate_validated_blueprint
from modsmith.verifier.gradle import build_with_retry
from modsmith.packager.archive import package_output


def run_pipeline(
    user_input: str,
    project_dir: Path,
    output_dir: Path,
    max_retries: int = 3,
) -> dict | None:
    """Complete pipeline: generate blueprint → generate project → compile verification → package output.

    Args:
        user_input: User's natural language description.
        project_dir: Generated project directory.
        output_dir: Packaging output directory.
        max_retries: Maximum number of retries.

    Returns:
        Packaging result dictionary, or None on failure.
    """
    print(f"🧠 Parsing user description: {user_input}")
    blueprint = generate_validated_blueprint(user_input, max_retries=max_retries)
    print(f"✅ Blueprint generated: {blueprint['mod_id']}")

    print(f"\n🔨 Starting project generation and compilation...")
    success, final_blueprint = build_with_retry(blueprint, project_dir, max_retries)

    if not success:
        print("❌ Compilation failed, cannot continue packaging.")
        return None

    print(f"\n📦 Starting packaging...")
    results = package_output(final_blueprint, project_dir, output_dir)
    print(f"\n🎉 All done! Output directory: {output_dir}")
    return results

Step 5: Create the test script test_package.py

"""Test the packaging output module."""

from pathlib import Path

from modsmith.pipeline import run_pipeline

results = run_pipeline(
    user_input="Create an apple that restores 4 hunger points when eaten",
    project_dir=Path("./pipeline_project"),
    output_dir=Path("./pipeline_output"),
)

if results:
    print("\nPackaging results:")
    for key, path in results.items():
        print(f"  {key}: {path}")
else:
    print("Packaging failed.")

Run:

python test_package.py

Expected result:

  1. Blueprint is generated.
  2. Project is generated and compiled.
  3. Packaging output goes to pipeline_output/, containing:
    • example-mod-src.zip
    • example-mod-1.0.0.jar (or similar name)
    • example-mod-blueprint.json
    • README.md

Step 6: Verify the output

Enter pipeline_output/:

ls -lh pipeline_output/

You should see:

  • Source zip file (tens of KB)
  • Compiled jar file (tens of KB)
  • Blueprint JSON
  • README.md

Test the jar in-game (optional):

  1. Install Fabric Loader and Fabric API.
  2. Copy the jar to .minecraft/mods/.
  3. Launch the game and find the item in the "Ingredients" tab of the creative inventory.

Step 7: Common issues

Problem Cause Solution
Jar not found Compilation failed or jar not generated Check whether build/libs/ contains a jar and confirm compilation succeeded
Zip file too large Includes build/ or .gradle/ Confirm the exclusion logic in _zip_project is effective
README missing blueprint blueprint parameter is empty Confirm a validated blueprint is passed in
Chinese garbled text File encoding issue Use encoding="utf-8" for all text files
Path too long Windows path length limit Shorten the output directory path, or use a shorter project name

Step 8: Task 10 Acceptance Criteria

  • modsmith/packager/archive.py is implemented, containing package_output, _zip_project, _find_jar, and _write_readme.
  • modsmith/pipeline.py is implemented, integrating blueprint generation, project generation, compile verification, and packaging output.
  • test_package.py runs end to end, and the output directory contains zip, jar, blueprint, and README.
  • The source zip excludes build/, .gradle/, and other irrelevant directories.
  • The jar file can be copied to the output directory and directly installed into the game.

After completing this task, your ModSmith will be able to generate a ready-to-use mod jar from a single sentence description. Next, Task 11 will implement the CLI entrypoint, wrapping the whole flow into the modsmith generate "description" command.

Clone this wiki locally