-
Notifications
You must be signed in to change notification settings - Fork 0
3.10 Implementing Packaging Output
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.
- 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
shutilandzipfileare installed (no extra installation needed).
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/tooutput_dir/. - The blueprint JSON is saved as
<mod_id>-blueprint.json. - A short
README.mdis generated automatically.
"""packager""""""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_projectpackages the source, excludingbuild/,.gradle/,.idea/, and other irrelevant directories. -
_find_jarlooks for a non-sources, non-dev jar underbuild/libs/. -
_write_readmegenerates installation instructions, including the complete blueprint JSON.
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"""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.pyExpected result:
- Blueprint is generated.
- Project is generated and compiled.
- Packaging output goes to
pipeline_output/, containing:example-mod-src.zip-
example-mod-1.0.0.jar(or similar name) example-mod-blueprint.jsonREADME.md
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):
- Install Fabric Loader and Fabric API.
- Copy the jar to
.minecraft/mods/. - Launch the game and find the item in the "Ingredients" tab of the creative inventory.
| 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 |
-
modsmith/packager/archive.pyis implemented, containingpackage_output,_zip_project,_find_jar, and_write_readme. -
modsmith/pipeline.pyis implemented, integrating blueprint generation, project generation, compile verification, and packaging output. -
test_package.pyruns 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.