mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 09:37:29 +00:00
FINALIZED ALL MIGRATION TOOLS NEEDED BEFORE EXECUTION
This commit is contained in:
1 parent
e26f8a4a01
commit
f4aa04dcb6
8 files changed
+8800
No files matched your search
@@ -0,0 +1,871 @@
|
||||
# MIGRATION EXECUTION PLAN — Core Extraction Wave 1
|
||||
|
||||
> **Created:** 2026-03-16
|
||||
> **Status:** PLANNING (not yet executing)
|
||||
> **Scope:** Whitelisted packages → core/ via the migration toolchain
|
||||
> **Data Sources:** `mc_type_classifications_original.json` (37 curated types), `all_methods_export.json` (member data), `mc_usage_index.json` (usage index)
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
This plan extracts ~250 files from 8 whitelisted mc1710 packages into `core/`, replacing all MC/Forge type references with platform-api abstractions. It is the **first full-scale use of the migration toolchain** and is structured into 4 sequential phases:
|
||||
|
||||
| Phase | What | Tool | Output |
|
||||
|-------|------|------|--------|
|
||||
| **1** | Create PA abstractions for all MC types used in scope | `pa_batch.py` | ~20 new PA files in `platform-api/` |
|
||||
| **2** | Copy whitelisted files to core/ | `Copy-Recursive.ps1` | ~150 new files in `core/` |
|
||||
| **3** | Replace MC types with PA abstractions in core/ | `Symbol-Swap`, `Static-Transform`, `Nuke-Imports` | All MC imports eliminated from core/ |
|
||||
| **4** | Verify, diagnose, stabilize | `Build-Report`, `migration-diff` | Clean `:core:compileJava` build |
|
||||
|
||||
**Estimated new tool development before execution:** 1 new Python script (`generate_pa_manifest.py`)
|
||||
|
||||
---
|
||||
|
||||
## 0. Current State Snapshot
|
||||
|
||||
### Already Migrated (DO NOT re-copy)
|
||||
|
||||
| Location | Count | Contents |
|
||||
|----------|-------|----------|
|
||||
| `platform-api/` | 239 files | Entity interfaces (IPlayer, IEntity, etc.), handlers, events, abilities, NBT, overlays, GUI, roles, scoreboard |
|
||||
| `core/` | 141 files | 8 controllers (Faction, Transport, Tag, Magic, GlobalData, CategoryManager, APIRegistry, ServerTagMap), 30+ data classes, 40+ enums/constants, 6 utils, network enums |
|
||||
|
||||
### Whitelisted Migration Scope (mc1710 → core/)
|
||||
|
||||
| Package | Files in mc1710 | Already in core/ | Net New |
|
||||
|---------|----------------|-----------------|---------|
|
||||
| `kamkeel/npcs/controllers/` (+ data/) | 89 | ~14 (ability data/enums/profile/telegraph) | ~75 |
|
||||
| `noppes/npcs/controllers/` (+ data/) | 98 | 38 (8 controllers + 30 data classes) | ~60 |
|
||||
| `noppes/npcs/quests/` | 6 | 0 | 6 |
|
||||
| `noppes/npcs/roles/` | 24 | 0 | 24 |
|
||||
| `noppes/npcs/constants/` | 8 | 40 (most already migrated) | ~3 |
|
||||
| `noppes/npcs/util/` | 10 (3 MC-free) | 6 | ~4 |
|
||||
| `noppes/npcs/config/` | 15 | 0 | 15 |
|
||||
| `kamkeel/npcs/network/` | 100+ | 9 (enums only) | **0** (all MC-dependent, skip) |
|
||||
| **TOTAL** | **~350** | **~107** | **~187** |
|
||||
|
||||
### What's NOT in scope (explicitly excluded)
|
||||
|
||||
- `containers/`, `guis/`, `gui/`, `fx/` — Client GUI code
|
||||
- `key/`, `model/`, `renderer/` — Client rendering
|
||||
- Proxies, Event Handlers — Platform-specific lifecycle
|
||||
- `kamkeel/npcs/network/` packets — All MC-dependent (100+ files)
|
||||
- Ability type classes (extend MC Entity) — Stay in mc1710
|
||||
- Script system — Deeply coupled, not planned
|
||||
|
||||
---
|
||||
|
||||
## 1. MC Type Classification Registry
|
||||
|
||||
### Authoritative Source: `mc_type_classifications_original.json`
|
||||
|
||||
This is the **hand-curated** classification of all 400 MC types. The auto-generated `classification_results.json` has inflated counts (46 INTERFACE vs 15, 95 SERVICE vs 12) because it lacks human judgment. **Always use the original.**
|
||||
|
||||
| Category | Count | Action |
|
||||
|----------|-------|--------|
|
||||
| EXISTING | 11 | Symbol-Swap ready — PA interfaces exist |
|
||||
| INTERFACE | 15 | Need new PA interface files |
|
||||
| SERVICE | 12 | Need PlatformService method additions |
|
||||
| TRANSFORM | 10 | Need Static-Transform regex + support classes |
|
||||
| SUPPRESS | 352 | Never abstract — code stays in mc1710 |
|
||||
|
||||
### 1A. EXISTING Types (11) — Symbol-Swap Ready
|
||||
|
||||
These already have PA abstractions. Just run Symbol-Swap.
|
||||
|
||||
| MC Type | PA Type | PA Package | Imports |
|
||||
|---------|---------|------------|---------|
|
||||
| `NBTTagCompound` | `INbt` | `noppes.npcs.api` | 593 |
|
||||
| `EntityPlayer` | `IPlayer` | `noppes.npcs.api.entity` | 551 |
|
||||
| `EntityPlayerMP` | `IPlayer` | `noppes.npcs.api.entity` | 304 |
|
||||
| `Entity` | `IEntity` | `noppes.npcs.api.entity` | 262 |
|
||||
| `ItemStack` | `IItemStack` | `noppes.npcs.api.item` | 228 |
|
||||
| `EntityLivingBase` | `IEntityLivingBase` | `noppes.npcs.api.entity` | 201 |
|
||||
| `World` | `IWorld` | `noppes.npcs.api` | 172 |
|
||||
| `NBTTagList` | `INbtList` | `noppes.npcs.api` | 111 |
|
||||
| `DamageSource` | `IDamageSource` | `noppes.npcs.api` | 36 |
|
||||
| `WorldServer` | `IWorld` | `noppes.npcs.api` | 18 |
|
||||
| `EntityLiving` | `IEntityLiving` | `noppes.npcs.api.entity` | 18 |
|
||||
|
||||
### 1B. INTERFACE Types (15) — Need PA Creation
|
||||
|
||||
New PA interfaces to create in `platform-api/`. Package rule: `net.minecraft.X` → `common.minecraft.X`, `net.minecraftforge.X` → `common.minecraftforge.X`, `cpw.mods.X` → `common.minecraftforge.X`.
|
||||
|
||||
| MC Type | PA Name | New Package | Imports | Priority for Scope |
|
||||
|---------|---------|-------------|---------|-------------------|
|
||||
| `ResourceLocation` | `IResourceLocation` | `common.minecraft.util` | 145 | **HIGH** — controllers, config |
|
||||
| `ICommandSender` | `ICommandSender` | `common.minecraft.command` | 47 | **HIGH** — controllers |
|
||||
| `ChatComponentText` | `ITextComponent` | `common.minecraft.util` | 20 | **HIGH** — controllers (messages) |
|
||||
| `ChatComponentTranslation` | `ITextComponent` | `common.minecraft.util` | 24 | **HIGH** — controllers (messages) |
|
||||
| `IChatComponent` | `ITextComponent` | `common.minecraft.util` | 10 | **HIGH** — base type |
|
||||
| `NBTBase` | `INbtBase` | `common.minecraft.nbt` | 13 | **HIGH** — data classes |
|
||||
| `Configuration` | `IConfiguration` | `common.minecraftforge.common.config` | 9 | **HIGH** — config system |
|
||||
| `Property` | `IConfigProperty` | `common.minecraftforge.common.config` | 7 | **HIGH** — config system |
|
||||
| `AxisAlignedBB` | `IBoundingBox` | `common.minecraft.util` | 59 | **MEDIUM** — some controllers |
|
||||
| `Vec3` | `IVector3` | `common.minecraft.util` | 43 | **MEDIUM** — some controllers |
|
||||
| `MovingObjectPosition` | `IRayTraceResult` | `common.minecraft.util` | 15 | **LOW** — mostly abilities |
|
||||
| `ChatStyle` | `ITextStyle` | `common.minecraft.util` | 3 | **LOW** — text formatting |
|
||||
| `Potion` | `IPotionType` | `common.minecraft.potion` | 7 | **MEDIUM** — effects system |
|
||||
| `PotionEffect` | `IPotionEffect` | `common.minecraft.potion` | 6 | **MEDIUM** — effects system |
|
||||
| `Explosion` | `IExplosion` | `common.minecraft.world` | 3 | **LOW** — abilities only |
|
||||
|
||||
**Filtering required**: Run `type-usage <Type> --scope` on each to confirm they're actually used in the whitelisted packages before creating PAs. Types with LOW scope relevance may be deferred.
|
||||
|
||||
### 1C. SERVICE Types (12) — PlatformService Method Additions
|
||||
|
||||
These are static utilities. Their methods get added to `PlatformService` (or `NBTIO`). No separate files needed — just interface method additions.
|
||||
|
||||
| MC Type | Routes To | Imports | Key Methods | Scope Relevance |
|
||||
|---------|-----------|---------|-------------|-----------------|
|
||||
| `StatCollector` | `PlatformService` | 106 | `translateToLocal()`, `translateToLocalFormatted()` | **HIGH** |
|
||||
| `Item` | `PlatformService` | 72 | `getItemById()`, `getIdFromItem()` | **MEDIUM** |
|
||||
| `MinecraftServer` | `PlatformService` | 26 | `getServer()`, `isDedicatedServer()`, `worldServerForDimension()` | **HIGH** |
|
||||
| `EntityList` | `PlatformService` | 25 | `getEntityString()`, `createEntityByName()` | **MEDIUM** |
|
||||
| `FMLCommonHandler` | `PlatformService` | 25 | `getSide()`, `getEffectiveSide()` | **HIGH** |
|
||||
| `CompressedStreamTools` | `NBTIO` | 24 | `readCompressed()`, `writeCompressed()` | **HIGH** |
|
||||
| `CommandBase` | `PlatformService` | 14 | `parseDouble()`, `parseInt()` | **LOW** |
|
||||
| `Loader` | `PlatformService` | 11 | `isModLoaded()` | **HIGH** |
|
||||
| `I18n` | `PlatformService` | 9 | `format()` | **LOW** (client) |
|
||||
| `DimensionManager` | `PlatformService` | 2 | `getWorld()` | **LOW** |
|
||||
| `PlayerSelector` | `PlatformService` | 2 | `matchPlayers()` | **LOW** |
|
||||
| `NBTUtil` | `NBTIO` | 1 | GameProfile NBT | **LOW** |
|
||||
|
||||
### 1D. TRANSFORM Types (10) — Static-Transform Patterns
|
||||
|
||||
These get handled by regex replacement. Some need support classes created in `core/`.
|
||||
|
||||
| MC Type | Transform Target | Imports | Support Class Needed |
|
||||
|---------|-----------------|---------|---------------------|
|
||||
| `Side` + `SideOnly` | `@ClientOnly`/`@ServerOnly` | 800 | Already exist in platform-api |
|
||||
| `MathHelper` | `ValueUtil.*` / `Math.*` | 98 | `ValueUtil` already in core/ |
|
||||
| `EnumChatFormatting` | `TextFormatting.*` | 19 | **YES** — `TextFormatting` enum in core/ |
|
||||
| `Constants` (Forge NBT) | `NbtConstants.*` | 9 | **YES** — `NbtConstants` class in core/ |
|
||||
| `CommandException` | Same name, new package | 31 | **YES** — `CommandException` in platform-api |
|
||||
| `SharedMonsterAttributes` | `Attributes.*` | 10 | **YES** — `Attributes` constants in core/ |
|
||||
| `EnumFacing` | `Direction.*` | 3 | **YES** — `Direction` enum in core/ |
|
||||
| `ChunkCoordinates` | `BlockPos`/inline | 4 | Uses existing `IPos` |
|
||||
| `EnumAction` | `ItemAction.*` | 11 | **YES** — `ItemAction` enum in core/ |
|
||||
|
||||
**Support classes to create (6):** TextFormatting, NbtConstants, CommandException, Attributes, Direction, ItemAction
|
||||
|
||||
---
|
||||
|
||||
## 2. Phase 1 — PA Abstraction Creation
|
||||
|
||||
### Goal
|
||||
Create ALL required PA interfaces and support classes BEFORE copying any files. This ensures Symbol-Swap has targets.
|
||||
|
||||
### Step 1.1: Scope Verification (NEW TOOL)
|
||||
|
||||
Before creating PAs, verify which INTERFACE/SERVICE types are actually used in the whitelisted packages. This prevents creating unused abstractions.
|
||||
|
||||
```bash
|
||||
# For each INTERFACE type, check if it's used in scope
|
||||
python tools/migration/analyze.py type-usage ResourceLocation --scope noppes/npcs/controllers/ --json
|
||||
python tools/migration/analyze.py type-usage ResourceLocation --scope noppes/npcs/config/ --json
|
||||
python tools/migration/analyze.py type-usage ResourceLocation --scope noppes/npcs/roles/ --json
|
||||
# ... repeat for all types × all packages
|
||||
```
|
||||
|
||||
**⚠️ TOOLING GAP:** This is manual and slow. We need `generate_pa_manifest.py` (see Section 6).
|
||||
|
||||
### Step 1.2: Create INTERFACE PA Files
|
||||
|
||||
Single `pa_batch.py` manifest with ALL INTERFACE PA files. Package mapping:
|
||||
- `net.minecraft.*` → `common.minecraft.*`
|
||||
- `net.minecraftforge.*` → `common.minecraftforge.*`
|
||||
- `cpw.mods.*` → `common.minecraftforge.*`
|
||||
|
||||
**Root:** `platform-api/src/main/java`
|
||||
|
||||
**Manifest structure (example entries):**
|
||||
```json
|
||||
{
|
||||
"root": "platform-api/src/main/java",
|
||||
"files": [
|
||||
{
|
||||
"content": "package common.minecraft.util;\n\npublic interface IResourceLocation {\n String getResourceDomain();\n String getResourcePath();\n String toString();\n}\n"
|
||||
},
|
||||
{
|
||||
"content": "package common.minecraft.util;\n\npublic interface ITextComponent {\n String getText();\n String getFormattedText();\n String getUnformattedText();\n ITextComponent appendSibling(ITextComponent sibling);\n ITextComponent setChatStyle(ITextStyle style);\n}\n"
|
||||
},
|
||||
{
|
||||
"content": "package common.minecraft.nbt;\n\npublic interface INbtBase {\n byte getId();\n INbtBase copy();\n}\n"
|
||||
},
|
||||
{
|
||||
"content": "package common.minecraftforge.common.config;\n\npublic interface IConfiguration {\n IConfigProperty get(String category, String key, String defaultValue, String comment);\n IConfigProperty get(String category, String key, int defaultValue, String comment);\n IConfigProperty get(String category, String key, boolean defaultValue, String comment);\n IConfigProperty get(String category, String key, double defaultValue, String comment);\n void save();\n void load();\n boolean hasKey(String category, String key);\n boolean hasCategory(String category);\n}\n"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Method content source:** `all_methods_export.json` — this file has EVERY method/field/static for each MC type with usage counts. The manifest generator reads this to include only methods with usage > 0.
|
||||
|
||||
### Step 1.3: Add SERVICE Methods to PlatformService
|
||||
|
||||
For SERVICE types, we add methods to the existing `PlatformService` interface. Use `pa_editor.py` to insert method declarations.
|
||||
|
||||
**Methods to add to `kamkeel/npcs/platform/PlatformService.java`:**
|
||||
|
||||
```java
|
||||
// StatCollector (106 imports)
|
||||
String translateToLocal(String key);
|
||||
String translateToLocalFormatted(String key, Object... args);
|
||||
|
||||
// MinecraftServer (26 imports)
|
||||
boolean isDedicatedServer(); // may already exist
|
||||
Object getServer();
|
||||
IWorld getWorldForDimension(int dimensionId);
|
||||
|
||||
// FMLCommonHandler (25 imports)
|
||||
boolean isPhysicalClient(); // already exists
|
||||
boolean isLogicalClient();
|
||||
|
||||
// Loader (11 imports)
|
||||
boolean isModLoaded(String modId);
|
||||
|
||||
// Item registry (72 imports)
|
||||
IItemStack getItemById(int id);
|
||||
int getIdFromItem(IItemStack item);
|
||||
|
||||
// EntityList (25 imports)
|
||||
String getEntityRegistryName(IEntity entity);
|
||||
IEntity createEntityByName(String name, IWorld world);
|
||||
```
|
||||
|
||||
**Methods to add to `noppes/npcs/platform/nbt/NBTIO.java`:**
|
||||
|
||||
```java
|
||||
// CompressedStreamTools (24 imports) — may already exist
|
||||
INbt readCompressed(java.io.InputStream stream);
|
||||
void writeCompressed(INbt nbt, java.io.OutputStream stream);
|
||||
```
|
||||
|
||||
### Step 1.4: Create TRANSFORM Support Classes
|
||||
|
||||
These go into `core/src/main/java/` (they're platform-independent).
|
||||
|
||||
```json
|
||||
{
|
||||
"root": "core/src/main/java",
|
||||
"files": [
|
||||
{
|
||||
"content": "package noppes.npcs.constants;\n\npublic class TextFormatting {\n public static final String BLACK = \"\\u00a70\";\n public static final String DARK_BLUE = \"\\u00a71\";\n public static final String DARK_GREEN = \"\\u00a72\";\n public static final String DARK_AQUA = \"\\u00a73\";\n public static final String DARK_RED = \"\\u00a74\";\n public static final String DARK_PURPLE = \"\\u00a75\";\n public static final String GOLD = \"\\u00a76\";\n public static final String GRAY = \"\\u00a77\";\n public static final String DARK_GRAY = \"\\u00a78\";\n public static final String BLUE = \"\\u00a79\";\n public static final String GREEN = \"\\u00a7a\";\n public static final String AQUA = \"\\u00a7b\";\n public static final String RED = \"\\u00a7c\";\n public static final String LIGHT_PURPLE = \"\\u00a7d\";\n public static final String YELLOW = \"\\u00a7e\";\n public static final String WHITE = \"\\u00a7f\";\n public static final String OBFUSCATED = \"\\u00a7k\";\n public static final String BOLD = \"\\u00a7l\";\n public static final String STRIKETHROUGH = \"\\u00a7m\";\n public static final String UNDERLINE = \"\\u00a7n\";\n public static final String ITALIC = \"\\u00a7o\";\n public static final String RESET = \"\\u00a7r\";\n}\n"
|
||||
},
|
||||
{
|
||||
"content": "package noppes.npcs.constants;\n\npublic class NbtConstants {\n public static final int TAG_END = 0;\n public static final int TAG_BYTE = 1;\n public static final int TAG_SHORT = 2;\n public static final int TAG_INT = 3;\n public static final int TAG_LONG = 4;\n public static final int TAG_FLOAT = 5;\n public static final int TAG_DOUBLE = 6;\n public static final int TAG_BYTE_ARRAY = 7;\n public static final int TAG_STRING = 8;\n public static final int TAG_LIST = 9;\n public static final int TAG_COMPOUND = 10;\n public static final int TAG_INT_ARRAY = 11;\n}\n"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Additional support classes: `CommandException`, `Attributes`, `Direction`, `ItemAction` — all simple enums/classes with no MC dependencies.
|
||||
|
||||
### Step 1.5: Execution Sequence
|
||||
|
||||
```
|
||||
1. Write PA INTERFACE manifest → pa_batch.py --dry-run → pa_batch.py --force
|
||||
2. Edit PlatformService with SERVICE methods → pa_editor.py --force
|
||||
3. Write TRANSFORM support class manifest → pa_batch.py --force
|
||||
4. Verify: ls platform-api/src/main/java/common/ — confirm structure
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Phase 2 — Core File Copy
|
||||
|
||||
### Goal
|
||||
Copy all files from whitelisted mc1710 packages to core/, following import dependencies within scope.
|
||||
|
||||
### Step 2.1: Build the Sources List
|
||||
|
||||
Every `.java` file in the whitelisted packages that is NOT already in core/.
|
||||
|
||||
**Initial Sources** (explicit files to copy — the roots of the recursion):
|
||||
|
||||
```powershell
|
||||
# noppes/npcs/controllers/ — files NOT already in core/
|
||||
$controllerSources = @(
|
||||
'noppes/npcs/controllers/QuestController.java',
|
||||
'noppes/npcs/controllers/PlayerQuestController.java',
|
||||
'noppes/npcs/controllers/PlayerDataController.java',
|
||||
'noppes/npcs/controllers/DialogController.java',
|
||||
'noppes/npcs/controllers/LinkedItemController.java',
|
||||
'noppes/npcs/controllers/LinkedNpcController.java',
|
||||
'noppes/npcs/controllers/PartyController.java',
|
||||
'noppes/npcs/controllers/AuctionController.java',
|
||||
'noppes/npcs/controllers/CustomEffectController.java',
|
||||
'noppes/npcs/controllers/CustomGuiController.java',
|
||||
'noppes/npcs/controllers/BankController.java',
|
||||
'noppes/npcs/controllers/ChunkController.java',
|
||||
'noppes/npcs/controllers/RecipeController.java',
|
||||
'noppes/npcs/controllers/SpawnController.java',
|
||||
'noppes/npcs/controllers/ServerCloneController.java',
|
||||
'noppes/npcs/controllers/ScriptController.java',
|
||||
'noppes/npcs/controllers/ScriptHookController.java',
|
||||
'noppes/npcs/controllers/AnimationController.java',
|
||||
'noppes/npcs/controllers/MarketRegistry.java',
|
||||
'noppes/npcs/controllers/HookDefinition.java'
|
||||
# ... + ALL data/* files not already in core/
|
||||
)
|
||||
|
||||
# noppes/npcs/quests/ — all 6 files
|
||||
$questSources = @(
|
||||
'noppes/npcs/quests/QuestItem.java',
|
||||
'noppes/npcs/quests/QuestKill.java',
|
||||
'noppes/npcs/quests/QuestLocation.java',
|
||||
'noppes/npcs/quests/QuestDialog.java',
|
||||
'noppes/npcs/quests/QuestManual.java',
|
||||
'noppes/npcs/quests/QuestInterface.java'
|
||||
)
|
||||
|
||||
# noppes/npcs/roles/ — all 24 files
|
||||
$roleSources = @(
|
||||
'noppes/npcs/roles/RoleTrader.java',
|
||||
'noppes/npcs/roles/RoleBank.java',
|
||||
'noppes/npcs/roles/RoleFollower.java',
|
||||
'noppes/npcs/roles/RoleCompanion.java',
|
||||
# ... all 24 files
|
||||
)
|
||||
|
||||
# noppes/npcs/config/ — all 15 files
|
||||
$configSources = @(
|
||||
'noppes/npcs/config/ConfigMain.java',
|
||||
'noppes/npcs/config/ConfigClient.java',
|
||||
'noppes/npcs/config/ConfigScript.java',
|
||||
'noppes/npcs/config/ConfigMarket.java',
|
||||
'noppes/npcs/config/ConfigEnergy.java',
|
||||
'noppes/npcs/config/ConfigItem.java',
|
||||
'noppes/npcs/config/ConfigDebug.java',
|
||||
'noppes/npcs/config/ConfigExperimental.java',
|
||||
'noppes/npcs/config/ConfigMixin.java',
|
||||
'noppes/npcs/config/LoadConfiguration.java',
|
||||
'noppes/npcs/config/StringCache.java',
|
||||
'noppes/npcs/config/GlyphCache.java',
|
||||
'noppes/npcs/config/legacy/LegacyLoader.java',
|
||||
'noppes/npcs/config/legacy/LegacyConfig.java',
|
||||
'noppes/npcs/config/legacy/ConfigProp.java'
|
||||
)
|
||||
|
||||
# MC-free util files only
|
||||
$utilSources = @(
|
||||
'noppes/npcs/util/GameProfileAlt.java',
|
||||
'noppes/npcs/util/JsonException.java',
|
||||
'noppes/npcs/util/ScriptToStringHelper.java'
|
||||
)
|
||||
|
||||
# kamkeel/npcs/controllers/ — files NOT already in core/
|
||||
$kamkeelSources = @(
|
||||
'kamkeel/npcs/controllers/AbilityController.java',
|
||||
'kamkeel/npcs/controllers/EnergyController.java',
|
||||
'kamkeel/npcs/controllers/ProfileController.java',
|
||||
'kamkeel/npcs/controllers/AttributeController.java',
|
||||
'kamkeel/npcs/controllers/TelegraphController.java',
|
||||
'kamkeel/npcs/controllers/SyncController.java'
|
||||
# ... + data/* files not already in core/
|
||||
)
|
||||
```
|
||||
|
||||
### Step 2.2: AllowPaths & AllowRecursePaths
|
||||
|
||||
```powershell
|
||||
$AllowPaths = @(
|
||||
'noppes/npcs/controllers/',
|
||||
'noppes/npcs/quests/',
|
||||
'noppes/npcs/roles/',
|
||||
'noppes/npcs/constants/',
|
||||
'noppes/npcs/util/',
|
||||
'noppes/npcs/config/',
|
||||
'kamkeel/npcs/controllers/',
|
||||
'kamkeel/npcs/util/',
|
||||
'kamkeel/npcs/config/'
|
||||
)
|
||||
|
||||
# Only recurse into these — prevent explosion into entity/client/network
|
||||
$AllowRecursePaths = @(
|
||||
'noppes/npcs/controllers/',
|
||||
'noppes/npcs/quests/',
|
||||
'noppes/npcs/roles/',
|
||||
'noppes/npcs/constants/',
|
||||
'noppes/npcs/config/',
|
||||
'kamkeel/npcs/controllers/'
|
||||
)
|
||||
# util/ is in AllowPaths but NOT AllowRecursePaths — files get copied but imports aren't chased
|
||||
```
|
||||
|
||||
### Step 2.3: Execute
|
||||
|
||||
**IMPORTANT:** `McRoot` defaults to `mc1710/src/main/java` but current code lives at `src/main/java`. Override required.
|
||||
|
||||
```powershell
|
||||
Copy-Recursive `
|
||||
-Sources ($controllerSources + $questSources + $roleSources + $configSources + $utilSources + $kamkeelSources) `
|
||||
-AllowPaths $AllowPaths `
|
||||
-AllowRecursePaths $AllowRecursePaths `
|
||||
-McRoot 'src/main/java'
|
||||
```
|
||||
|
||||
**Expected output:** ~150-187 files copied to `core/src/main/java/`.
|
||||
|
||||
### Step 2.4: What Copy-Recursive Will Skip
|
||||
|
||||
Files already in core/ will be reported as EXISTS (not re-copied). Files outside AllowPaths will be reported as SKIPPED. This is correct behavior.
|
||||
|
||||
---
|
||||
|
||||
## 4. Phase 3 — Symbol Replacement
|
||||
|
||||
### Goal
|
||||
Replace every MC/Forge type reference in core/ with PA abstractions.
|
||||
|
||||
### Step 3.1: Nuke-Imports (first)
|
||||
|
||||
Remove all MC/Forge/platform-specific imports from core/.
|
||||
|
||||
```powershell
|
||||
Nuke-Imports -Directory 'core/src/main/java' -ForbiddenPrefixes @(
|
||||
'net.minecraft.',
|
||||
'cpw.mods.',
|
||||
'net.minecraftforge.',
|
||||
'noppes.npcs.entity.',
|
||||
'noppes.npcs.client.',
|
||||
'noppes.npcs.mixin.',
|
||||
'kamkeel.npcs.network.',
|
||||
'kamkeel.npcs.entity.',
|
||||
'noppes.npcs.containers.',
|
||||
'noppes.npcs.scripted.',
|
||||
'noppes.npcs.janino.',
|
||||
'noppes.npcs.wrapper.',
|
||||
'noppes.npcs.blocks.',
|
||||
'noppes.npcs.items.',
|
||||
'noppes.npcs.ai.'
|
||||
)
|
||||
```
|
||||
|
||||
### Step 3.2: Symbol-Swap — EXISTING Types (11)
|
||||
|
||||
```powershell
|
||||
Symbol-Swap -Directory 'core/src/main/java' -Swaps @(
|
||||
@('NBTTagCompound', 'INbt', 'net.minecraft.nbt.NBTTagCompound', 'noppes.npcs.api.INbt'),
|
||||
@('NBTTagList', 'INbtList', 'net.minecraft.nbt.NBTTagList', 'noppes.npcs.api.INbtList'),
|
||||
@('EntityPlayer', 'IPlayer', 'net.minecraft.entity.player.EntityPlayer', 'noppes.npcs.api.entity.IPlayer'),
|
||||
@('EntityPlayerMP', 'IPlayer', 'net.minecraft.entity.player.EntityPlayerMP', 'noppes.npcs.api.entity.IPlayer'),
|
||||
@('Entity', 'IEntity', 'net.minecraft.entity.Entity', 'noppes.npcs.api.entity.IEntity'),
|
||||
@('ItemStack', 'IItemStack', 'net.minecraft.item.ItemStack', 'noppes.npcs.api.item.IItemStack'),
|
||||
@('EntityLivingBase', 'IEntityLivingBase', 'net.minecraft.entity.EntityLivingBase', 'noppes.npcs.api.entity.IEntityLivingBase'),
|
||||
@('World', 'IWorld', 'net.minecraft.world.World', 'noppes.npcs.api.IWorld'),
|
||||
@('WorldServer', 'IWorld', 'net.minecraft.world.WorldServer', 'noppes.npcs.api.IWorld'),
|
||||
@('DamageSource', 'IDamageSource', 'net.minecraft.util.DamageSource', 'noppes.npcs.api.IDamageSource'),
|
||||
@('EntityLiving', 'IEntityLiving', 'net.minecraft.entity.EntityLiving', 'noppes.npcs.api.entity.IEntityLiving')
|
||||
)
|
||||
```
|
||||
|
||||
### Step 3.3: Symbol-Swap — NEW INTERFACE Types
|
||||
|
||||
```powershell
|
||||
Symbol-Swap -Directory 'core/src/main/java' -Swaps @(
|
||||
@('ResourceLocation', 'IResourceLocation', 'net.minecraft.util.ResourceLocation', 'common.minecraft.util.IResourceLocation'),
|
||||
@('ICommandSender', 'ICommandSender', 'net.minecraft.command.ICommandSender', 'common.minecraft.command.ICommandSender'),
|
||||
@('ChatComponentText', 'ITextComponent', 'net.minecraft.util.ChatComponentText', 'common.minecraft.util.ITextComponent'),
|
||||
@('ChatComponentTranslation', 'ITextComponent', 'net.minecraft.util.ChatComponentTranslation', 'common.minecraft.util.ITextComponent'),
|
||||
@('IChatComponent', 'ITextComponent', 'net.minecraft.util.IChatComponent', 'common.minecraft.util.ITextComponent'),
|
||||
@('ChatStyle', 'ITextStyle', 'net.minecraft.util.ChatStyle', 'common.minecraft.util.ITextStyle'),
|
||||
@('NBTBase', 'INbtBase', 'net.minecraft.nbt.NBTBase', 'common.minecraft.nbt.INbtBase'),
|
||||
@('Configuration', 'IConfiguration', 'net.minecraftforge.common.config.Configuration', 'common.minecraftforge.common.config.IConfiguration'),
|
||||
@('Property', 'IConfigProperty', 'net.minecraftforge.common.config.Property', 'common.minecraftforge.common.config.IConfigProperty'),
|
||||
@('AxisAlignedBB', 'IBoundingBox', 'net.minecraft.util.AxisAlignedBB', 'common.minecraft.util.IBoundingBox'),
|
||||
@('Vec3', 'IVector3', 'net.minecraft.util.Vec3', 'common.minecraft.util.IVector3'),
|
||||
@('MovingObjectPosition', 'IRayTraceResult', 'net.minecraft.util.MovingObjectPosition', 'common.minecraft.util.IRayTraceResult'),
|
||||
@('Potion', 'IPotionType', 'net.minecraft.potion.Potion', 'common.minecraft.potion.IPotionType'),
|
||||
@('PotionEffect', 'IPotionEffect', 'net.minecraft.potion.PotionEffect', 'common.minecraft.potion.IPotionEffect'),
|
||||
@('Explosion', 'IExplosion', 'net.minecraft.world.Explosion', 'common.minecraft.world.IExplosion')
|
||||
)
|
||||
```
|
||||
|
||||
### Step 3.4: Static-Transform — SERVICE Rerouting
|
||||
|
||||
```powershell
|
||||
Static-Transform -Directory 'core/src/main/java' -Transforms @(
|
||||
# NBT constructors
|
||||
@('new\s+NBTTagCompound\s*\(\)', 'NBT.compound()'),
|
||||
@('new\s+NBTTagList\s*\(\)', 'NBT.list()'),
|
||||
|
||||
# CompressedStreamTools → NBTIO
|
||||
@('CompressedStreamTools\.readCompressed\s*\(([^)]+)\)', 'NBTIO.readCompressed($1)'),
|
||||
@('CompressedStreamTools\.writeCompressed\s*\(([^,]+),\s*([^)]+)\)', 'NBTIO.writeCompressed($1, $2)'),
|
||||
|
||||
# StatCollector → PlatformServiceHolder
|
||||
@('StatCollector\.translateToLocal\s*\(([^)]+)\)', 'PlatformServiceHolder.get().translateToLocal($1)'),
|
||||
@('StatCollector\.translateToLocalFormatted\s*\(([^)]+)\)', 'PlatformServiceHolder.get().translateToLocalFormatted($1)'),
|
||||
|
||||
# MinecraftServer
|
||||
@('MinecraftServer\.getServer\s*\(\)', 'PlatformServiceHolder.get().getServer()'),
|
||||
|
||||
# FMLCommonHandler
|
||||
@('FMLCommonHandler\.instance\(\)\.getSide\(\)\s*==\s*Side\.CLIENT', 'PlatformServiceHolder.get().isPhysicalClient()'),
|
||||
@('FMLCommonHandler\.instance\(\)\.getEffectiveSide\(\)\s*==\s*Side\.CLIENT', 'PlatformServiceHolder.get().isLogicalClient()'),
|
||||
|
||||
# Loader
|
||||
@('Loader\.isModLoaded\s*\(([^)]+)\)', 'PlatformServiceHolder.get().isModLoaded($1)'),
|
||||
|
||||
# MathHelper
|
||||
@('MathHelper\.clamp_int\s*\(', 'ValueUtil.clampInt('),
|
||||
@('MathHelper\.clamp_float\s*\(', 'ValueUtil.clampFloat('),
|
||||
@('MathHelper\.clamp_double\s*\(', 'ValueUtil.clampDouble('),
|
||||
@('MathHelper\.sqrt_double\s*\(', 'Math.sqrt('),
|
||||
@('MathHelper\.sqrt_float\s*\(', '(float)Math.sqrt('),
|
||||
@('MathHelper\.floor_double\s*\(', 'ValueUtil.floorDouble('),
|
||||
@('MathHelper\.floor_float\s*\(', 'ValueUtil.floorFloat('),
|
||||
@('MathHelper\.sin\s*\(', '(float)Math.sin('),
|
||||
@('MathHelper\.cos\s*\(', '(float)Math.cos('),
|
||||
@('MathHelper\.abs\s*\(', 'Math.abs('),
|
||||
|
||||
# EnumChatFormatting
|
||||
@('EnumChatFormatting\.', 'TextFormatting.'),
|
||||
|
||||
# Constants.NBT
|
||||
@('Constants\.NBT\.', 'NbtConstants.'),
|
||||
|
||||
# SharedMonsterAttributes
|
||||
@('SharedMonsterAttributes\.maxHealth', 'Attributes.MAX_HEALTH'),
|
||||
@('SharedMonsterAttributes\.movementSpeed', 'Attributes.MOVEMENT_SPEED'),
|
||||
@('SharedMonsterAttributes\.attackDamage', 'Attributes.ATTACK_DAMAGE'),
|
||||
@('SharedMonsterAttributes\.followRange', 'Attributes.FOLLOW_RANGE'),
|
||||
@('SharedMonsterAttributes\.knockbackResistance', 'Attributes.KNOCKBACK_RESISTANCE'),
|
||||
|
||||
# Annotations
|
||||
@('@SideOnly\s*\(\s*Side\.CLIENT\s*\)', '@ClientOnly'),
|
||||
@('@SideOnly\s*\(\s*Side\.SERVER\s*\)', '@ServerOnly')
|
||||
)
|
||||
```
|
||||
|
||||
### Step 3.5: Generate-Stubs
|
||||
|
||||
Classes that will be referenced but aren't in scope. These are TEMPORARY placeholders.
|
||||
|
||||
```powershell
|
||||
Generate-Stubs -Stubs @(
|
||||
@{ pkg='noppes.npcs'; name='CustomNpcs'; type='class';
|
||||
body=' public static java.io.File Dir = new java.io.File(".");
|
||||
public static int DefaultInteractLine = 0;
|
||||
public static java.util.logging.Logger logger;' },
|
||||
@{ pkg='noppes.npcs'; name='EventHooks'; type='class';
|
||||
body=' // STUB - event dispatch stays in mc1710' },
|
||||
@{ pkg='noppes.npcs'; name='LogWriter'; type='class';
|
||||
body=' public static void info(String msg) {}
|
||||
public static void warn(String msg) {}
|
||||
public static void error(String msg) {}
|
||||
public static void error(String msg, Throwable e) {}' },
|
||||
@{ pkg='noppes.npcs.controllers'; name='SyncController'; type='class';
|
||||
body=' // STUB - sync stays in mc1710' },
|
||||
@{ pkg='noppes.npcs.entity'; name='EntityNPCInterface'; type='class';
|
||||
body=' // STUB - entity stays in mc1710' }
|
||||
)
|
||||
```
|
||||
|
||||
### Step 3.6: Dedupe-Imports
|
||||
|
||||
```powershell
|
||||
Dedupe-Imports -Directory 'core/src/main/java'
|
||||
```
|
||||
|
||||
### Step 3.7: All-in-One Wave Script
|
||||
|
||||
**All steps 3.1–3.6 go into a single `.ps1` file:**
|
||||
|
||||
```
|
||||
tools/migration/wave1_core_extraction.ps1
|
||||
```
|
||||
|
||||
Execute:
|
||||
```bash
|
||||
powershell -ExecutionPolicy Bypass -File "tools/migration/wave1_core_extraction.ps1"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Phase 4 — Verification & Stabilization
|
||||
|
||||
### Goal
|
||||
Get `:core:compileJava` to pass. Diagnose and fix all errors categorically.
|
||||
|
||||
### Step 4.1: Initial Build-Report
|
||||
|
||||
```powershell
|
||||
Build-Report -GradleTask ':core:compileJava' -MaxSamples 10
|
||||
```
|
||||
|
||||
**Expected error categories and fixes:**
|
||||
|
||||
| Category | Likely Cause | Fix |
|
||||
|----------|-------------|-----|
|
||||
| `MISSING_PACKAGE` | Leftover MC import Nuke-Imports missed | Re-run Nuke with expanded prefixes |
|
||||
| `MISSING_CLASS` | Transitive dep not in scope | Generate-Stubs |
|
||||
| `MISSING_METHOD` | PA interface missing method | Add method to PA interface via pa_editor |
|
||||
| `TYPE_MISMATCH` | Wrong PA type used | Check Symbol-Swap mapping |
|
||||
| `BAD_ARGS` | Method signature changed | Static-Transform or manual fix |
|
||||
| `ABSTRACT_VIOLATION` | Class implements MC interface | Remove `implements` or stub |
|
||||
|
||||
### Step 4.2: Batch migration-diff
|
||||
|
||||
Run `migration-diff` on ALL copied files to verify completeness:
|
||||
|
||||
```bash
|
||||
# For each migrated file:
|
||||
python tools/migration/analyze.py migration-diff \
|
||||
--old src/main/java/noppes/npcs/controllers/QuestController.java \
|
||||
--new core/src/main/java/noppes/npcs/controllers/QuestController.java \
|
||||
--json
|
||||
```
|
||||
|
||||
**⚠️ TOOLING GAP:** No batch migration-diff exists. Need to script a loop or build `batch_migration_diff.py`.
|
||||
|
||||
### Step 4.3: Store Diagnostic Cache
|
||||
|
||||
All outputs cached in JSON for easy navigation:
|
||||
|
||||
```bash
|
||||
# Build report
|
||||
python tools/migration/analyze.py coverage-report --json > tools/migration/_wave1_coverage.json
|
||||
|
||||
# All migration-diff results
|
||||
python tools/migration/_batch_migration_diff.py --output tools/migration/_wave1_diffs.json
|
||||
```
|
||||
|
||||
### Step 4.4: Fix Cycle
|
||||
|
||||
```
|
||||
WHILE build fails:
|
||||
1. Build-Report → categorize errors
|
||||
2. MISSING_PACKAGE → Nuke-Imports (add new prefixes)
|
||||
3. MISSING_CLASS → Generate-Stubs OR Create-And-Propagate (if PA needed)
|
||||
4. MISSING_METHOD → pa_editor (add to PA) OR Static-Transform (rename)
|
||||
5. TYPE_MISMATCH → Symbol-Swap (fix mapping) OR manual cast
|
||||
6. Dedupe-Imports
|
||||
7. Build-Report → verify error count dropped
|
||||
```
|
||||
|
||||
### Step 4.5: Final Verification
|
||||
|
||||
```bash
|
||||
# Build must pass
|
||||
./gradlew :core:compileJava
|
||||
|
||||
# Coverage should improve significantly
|
||||
python tools/migration/analyze.py coverage-report --detailed
|
||||
|
||||
# mc1710 build must also still pass
|
||||
./gradlew build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Missing Tools — Build Before Execution
|
||||
|
||||
### 6A. `generate_pa_manifest.py` (HIGH PRIORITY)
|
||||
|
||||
**Purpose:** Bridge the gap between analysis data → executable pa_batch manifest.
|
||||
|
||||
**Input:**
|
||||
- `mc_type_classifications_original.json` — which types need PAs
|
||||
- `all_methods_export.json` — what methods each type has
|
||||
- `mc_usage_index.json` — which files use which types
|
||||
- Package scope filter — list of package prefixes to restrict to
|
||||
|
||||
**Output:**
|
||||
- `pa_batch` manifest JSON with complete Java interface/class content
|
||||
- Swap table (old → new symbol mappings) for Symbol-Swap
|
||||
- Static-Transform pattern list for SERVICE types
|
||||
|
||||
**Key behavior:**
|
||||
1. Read classifications, filter to INTERFACE types
|
||||
2. For each INTERFACE type, filter to types used in specified packages (via usage index)
|
||||
3. For each filtered type, read methods from `all_methods_export.json`
|
||||
4. Generate Java interface content with correct package mapping:
|
||||
- `net.minecraft.X` → `common.minecraft.X`
|
||||
- `net.minecraftforge.X` → `common.minecraftforge.X`
|
||||
- `cpw.mods.X` → `common.minecraftforge.X`
|
||||
5. Output pa_batch manifest JSON
|
||||
|
||||
**Estimated development time:** 2-3 hours
|
||||
|
||||
**Why this is critical:** Without it, composing the manifest for ~15-20 PA interfaces is manual and error-prone. Each interface needs the RIGHT methods (from all_methods_export), the RIGHT package, and the RIGHT imports. This tool automates the entire process.
|
||||
|
||||
### 6B. `batch_migration_diff.py` (MEDIUM PRIORITY)
|
||||
|
||||
**Purpose:** Run `migration-diff` on ALL migrated files in one command, output unified JSON.
|
||||
|
||||
**Input:**
|
||||
- Source root (mc1710 path)
|
||||
- Target root (core path)
|
||||
- List of migrated file paths (or auto-discover by comparing directories)
|
||||
|
||||
**Output:**
|
||||
- JSON with per-file PASS/WARN/FAIL verdicts
|
||||
- Aggregate statistics
|
||||
- Cached for agent navigation
|
||||
|
||||
**Estimated development time:** 1-2 hours
|
||||
|
||||
### 6C. `generate_wave_script.py` (LOW PRIORITY — nice to have)
|
||||
|
||||
**Purpose:** Auto-generate the complete `.ps1` wave script from classification data.
|
||||
|
||||
**Input:**
|
||||
- Type classifications
|
||||
- Package scope
|
||||
- Phase parameters
|
||||
|
||||
**Output:**
|
||||
- Complete `.ps1` file with Copy-Recursive + Nuke + Swap + Transform + Stubs + Dedupe + Build
|
||||
|
||||
**Estimated development time:** 2-3 hours
|
||||
|
||||
**Why low priority:** The wave script can be composed manually from the plan. Automation saves time on FUTURE waves but is not blocking for Wave 1.
|
||||
|
||||
---
|
||||
|
||||
## 7. Execution Checklist
|
||||
|
||||
### Pre-Flight
|
||||
|
||||
- [ ] **Tool development:** Build `generate_pa_manifest.py`
|
||||
- [ ] **Scope verification:** Run `type-usage` for each INTERFACE/SERVICE type against whitelisted packages
|
||||
- [ ] **Confirm existing PAs:** Verify all 11 EXISTING types have complete PA interfaces (run `surface-miner --compare-existing`)
|
||||
- [ ] **Git clean state:** Ensure working tree is clean before starting
|
||||
|
||||
### Phase 1 Execution
|
||||
|
||||
- [ ] Generate PA manifest via `generate_pa_manifest.py`
|
||||
- [ ] Dry-run: `pa_batch.py manifest.json --dry-run`
|
||||
- [ ] Create PAs: `pa_batch.py manifest.json --force`
|
||||
- [ ] Edit PlatformService with SERVICE methods: `pa_editor.py`
|
||||
- [ ] Create TRANSFORM support classes: `pa_batch.py`
|
||||
- [ ] Verify: `ls platform-api/src/main/java/common/`
|
||||
|
||||
### Phase 2 Execution
|
||||
|
||||
- [ ] Write wave script with Copy-Recursive params
|
||||
- [ ] Execute: Copy ~150-187 files to core/
|
||||
- [ ] Verify: count files in core/ (should increase by ~150)
|
||||
|
||||
### Phase 3 Execution
|
||||
|
||||
- [ ] Execute wave script (Nuke + Swap + Transform + Stubs + Dedupe)
|
||||
- [ ] Initial Build-Report
|
||||
|
||||
### Phase 4 Execution
|
||||
|
||||
- [ ] Fix cycle until `:core:compileJava` passes
|
||||
- [ ] Run batch migration-diff
|
||||
- [ ] Store diagnostic cache
|
||||
- [ ] Run `coverage-report` — measure improvement
|
||||
- [ ] Verify mc1710 build still passes: `./gradlew build`
|
||||
- [ ] **Human commits** (agent does NOT commit)
|
||||
|
||||
---
|
||||
|
||||
## 8. Risk Assessment
|
||||
|
||||
| Risk | Impact | Mitigation |
|
||||
|------|--------|------------|
|
||||
| Script system files copied (deeply MC-coupled) | Build explosion | AllowRecursePaths prevents recursion into script packages |
|
||||
| Symbol-Swap replaces too aggressively (e.g., `Entity` → `IEntity` in non-type contexts) | Broken code | Word-boundary regex in Symbol-Swap handles this. Verify with migration-diff. |
|
||||
| Some controllers have too many MC deps to clean up | Phase 3 stalls | Generate-Stubs for anything not in scope. Fix cycle handles residuals. |
|
||||
| Config system needs Forge Configuration deeply | Config files can't compile | IConfiguration PA must match Forge API surface closely. Use surface-miner to verify. |
|
||||
| Role/Quest files reference EntityNPCInterface | Missing stubs | Generate-Stubs for EntityNPCInterface. Core version is a placeholder. |
|
||||
| Copy-Recursive pulls in too many transitive deps | Scope explosion | AllowPaths + AllowRecursePaths constrain the recursion tightly |
|
||||
|
||||
---
|
||||
|
||||
## 9. Success Criteria
|
||||
|
||||
| Metric | Before | Target |
|
||||
|--------|--------|--------|
|
||||
| Files in core/ | 141 | ~290-330 |
|
||||
| PA type coverage (types with abstractions) | 11/400 (2.8%) | ~30/400 (7.5%) |
|
||||
| PA import coverage (imports to abstracted types) | 37.1% | ~55-60% |
|
||||
| `:core:compileJava` | PASS | PASS |
|
||||
| `:build` (mc1710) | PASS | PASS |
|
||||
|
||||
---
|
||||
|
||||
## Appendix A: Complete Swap Table
|
||||
|
||||
This is the full `-Swaps` parameter for Symbol-Swap, combining EXISTING + new INTERFACE types.
|
||||
|
||||
```powershell
|
||||
$AllSwaps = @(
|
||||
# === EXISTING (11) ===
|
||||
@('NBTTagCompound', 'INbt', 'net.minecraft.nbt.NBTTagCompound', 'noppes.npcs.api.INbt'),
|
||||
@('NBTTagList', 'INbtList', 'net.minecraft.nbt.NBTTagList', 'noppes.npcs.api.INbtList'),
|
||||
@('EntityPlayer', 'IPlayer', 'net.minecraft.entity.player.EntityPlayer', 'noppes.npcs.api.entity.IPlayer'),
|
||||
@('EntityPlayerMP', 'IPlayer', 'net.minecraft.entity.player.EntityPlayerMP', 'noppes.npcs.api.entity.IPlayer'),
|
||||
@('Entity', 'IEntity', 'net.minecraft.entity.Entity', 'noppes.npcs.api.entity.IEntity'),
|
||||
@('ItemStack', 'IItemStack', 'net.minecraft.item.ItemStack', 'noppes.npcs.api.item.IItemStack'),
|
||||
@('EntityLivingBase', 'IEntityLivingBase', 'net.minecraft.entity.EntityLivingBase', 'noppes.npcs.api.entity.IEntityLivingBase'),
|
||||
@('World', 'IWorld', 'net.minecraft.world.World', 'noppes.npcs.api.IWorld'),
|
||||
@('WorldServer', 'IWorld', 'net.minecraft.world.WorldServer', 'noppes.npcs.api.IWorld'),
|
||||
@('DamageSource', 'IDamageSource', 'net.minecraft.util.DamageSource', 'noppes.npcs.api.IDamageSource'),
|
||||
@('EntityLiving', 'IEntityLiving', 'net.minecraft.entity.EntityLiving', 'noppes.npcs.api.entity.IEntityLiving'),
|
||||
|
||||
# === NEW INTERFACE (15) ===
|
||||
@('ResourceLocation', 'IResourceLocation', 'net.minecraft.util.ResourceLocation', 'common.minecraft.util.IResourceLocation'),
|
||||
@('ICommandSender', 'ICommandSender', 'net.minecraft.command.ICommandSender', 'common.minecraft.command.ICommandSender'),
|
||||
@('ChatComponentText', 'ITextComponent', 'net.minecraft.util.ChatComponentText', 'common.minecraft.util.ITextComponent'),
|
||||
@('ChatComponentTranslation', 'ITextComponent', 'net.minecraft.util.ChatComponentTranslation', 'common.minecraft.util.ITextComponent'),
|
||||
@('IChatComponent', 'ITextComponent', 'net.minecraft.util.IChatComponent', 'common.minecraft.util.ITextComponent'),
|
||||
@('ChatStyle', 'ITextStyle', 'net.minecraft.util.ChatStyle', 'common.minecraft.util.ITextStyle'),
|
||||
@('NBTBase', 'INbtBase', 'net.minecraft.nbt.NBTBase', 'common.minecraft.nbt.INbtBase'),
|
||||
@('Configuration', 'IConfiguration', 'net.minecraftforge.common.config.Configuration', 'common.minecraftforge.common.config.IConfiguration'),
|
||||
@('Property', 'IConfigProperty', 'net.minecraftforge.common.config.Property', 'common.minecraftforge.common.config.IConfigProperty'),
|
||||
@('AxisAlignedBB', 'IBoundingBox', 'net.minecraft.util.AxisAlignedBB', 'common.minecraft.util.IBoundingBox'),
|
||||
@('Vec3', 'IVector3', 'net.minecraft.util.Vec3', 'common.minecraft.util.IVector3'),
|
||||
@('MovingObjectPosition', 'IRayTraceResult', 'net.minecraft.util.MovingObjectPosition', 'common.minecraft.util.IRayTraceResult'),
|
||||
@('Potion', 'IPotionType', 'net.minecraft.potion.Potion', 'common.minecraft.potion.IPotionType'),
|
||||
@('PotionEffect', 'IPotionEffect', 'net.minecraft.potion.PotionEffect', 'common.minecraft.potion.IPotionEffect'),
|
||||
@('Explosion', 'IExplosion', 'net.minecraft.world.Explosion', 'common.minecraft.world.IExplosion')
|
||||
)
|
||||
```
|
||||
|
||||
## Appendix B: Static-Transform Registry
|
||||
|
||||
Complete list from Section 4, Step 3.4. All patterns use .NET regex syntax for PowerShell.
|
||||
|
||||
## Appendix C: File Inventory by Package
|
||||
|
||||
### Files to copy (not already in core/)
|
||||
|
||||
**noppes/npcs/controllers/** (estimated ~60 new):
|
||||
- QuestController, PlayerQuestController, PlayerDataController, DialogController
|
||||
- LinkedItemController, LinkedNpcController, PartyController, AuctionController
|
||||
- CustomEffectController, CustomGuiController, BankController, ChunkController
|
||||
- RecipeController, SpawnController, ServerCloneController, AnimationController
|
||||
- ScriptController, ScriptHookController, MarketRegistry, HookDefinition
|
||||
- + ~40 data/* classes (Quest, Dialog, PlayerData, etc.)
|
||||
|
||||
**noppes/npcs/quests/** (6 new):
|
||||
- QuestItem, QuestKill, QuestLocation, QuestDialog, QuestManual, QuestInterface
|
||||
|
||||
**noppes/npcs/roles/** (24 new):
|
||||
- RoleTrader, RoleBank, RoleFollower, RoleCompanion, RoleTransporter, RolePostman
|
||||
- RoleMount, RoleInterface, RoleInnkeeper, RoleAuctioneer
|
||||
- JobBard, JobGuard, JobHealer, JobSpawner, JobItemGiver, JobFollower, JobConversation, JobChunkLoader, JobInterface
|
||||
- companion/* (5 files)
|
||||
|
||||
**noppes/npcs/config/** (15 new):
|
||||
- ConfigMain, ConfigClient, ConfigScript, ConfigMarket, ConfigEnergy, ConfigItem
|
||||
- ConfigDebug, ConfigExperimental, ConfigMixin, LoadConfiguration, StringCache, GlyphCache
|
||||
- legacy/* (3 files)
|
||||
|
||||
**noppes/npcs/util/** (3 new MC-free):
|
||||
- GameProfileAlt, JsonException, ScriptToStringHelper
|
||||
|
||||
**kamkeel/npcs/controllers/** (estimated ~75 new):
|
||||
- AbilityController, EnergyController, ProfileController, AttributeController
|
||||
- TelegraphController, SyncController
|
||||
- + ~69 data/* classes (abilities, ability types, conditions, etc.)
|
||||
|
||||
**noppes/npcs/constants/** (~3 new):
|
||||
- ScriptContext, EnumPotionType, EnumNotificationType (others already in core/)
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,141 @@
|
||||
# ============================================================
|
||||
# Wave Migration Script
|
||||
# Generated by generate_wave_script.py on 2026-03-16
|
||||
# Source: tools/migration/mc_type_mappings.json
|
||||
# ============================================================
|
||||
|
||||
. (Join-Path $PSScriptRoot 'Copy-Recursive.ps1')
|
||||
. (Join-Path $PSScriptRoot 'Nuke-Imports.ps1')
|
||||
. (Join-Path $PSScriptRoot 'Symbol-Swap.ps1')
|
||||
. (Join-Path $PSScriptRoot 'Static-Transform.ps1')
|
||||
. (Join-Path $PSScriptRoot 'Generate-Stubs.ps1')
|
||||
. (Join-Path $PSScriptRoot 'Dedupe-Imports.ps1')
|
||||
. (Join-Path $PSScriptRoot 'Build-Report.ps1')
|
||||
|
||||
# ============================================================
|
||||
# STEP 2: Nuke MC imports
|
||||
# ============================================================
|
||||
Nuke-Imports -Directory 'core/src/main/java' -ForbiddenPrefixes @(
|
||||
'net.minecraft.',
|
||||
'cpw.mods.',
|
||||
'net.minecraftforge.',
|
||||
'noppes.npcs.entity.',
|
||||
'noppes.npcs.client.',
|
||||
'noppes.npcs.mixin.',
|
||||
'kamkeel.npcs.network.',
|
||||
'kamkeel.npcs.entity.',
|
||||
'noppes.npcs.containers.',
|
||||
'noppes.npcs.scripted.',
|
||||
'noppes.npcs.janino.',
|
||||
'noppes.npcs.wrapper.',
|
||||
'noppes.npcs.blocks.',
|
||||
'noppes.npcs.items.',
|
||||
'noppes.npcs.ai.'
|
||||
)
|
||||
|
||||
# ============================================================
|
||||
# STEP 3: Symbol-Swap — replace MC types with PA abstractions
|
||||
# ============================================================
|
||||
Symbol-Swap -Directory 'core/src/main/java' -Swaps @(
|
||||
@('NBTTagCompound', 'INbt', 'net.minecraft.nbt.NBTTagCompound', 'noppes.npcs.api.INbt'),
|
||||
@('EntityPlayer', 'IPlayer', 'net.minecraft.entity.player.EntityPlayer', 'noppes.npcs.api.entity.IPlayer'),
|
||||
@('EntityPlayerMP', 'IPlayer', 'net.minecraft.entity.player.EntityPlayerMP', 'noppes.npcs.api.entity.IPlayer'),
|
||||
@('Entity', 'IEntity', 'net.minecraft.entity.Entity', 'noppes.npcs.api.entity.IEntity'),
|
||||
@('ItemStack', 'IItemStack', 'net.minecraft.item.ItemStack', 'noppes.npcs.api.item.IItemStack'),
|
||||
@('EntityLivingBase', 'IEntityLivingBase', 'net.minecraft.entity.EntityLivingBase', 'noppes.npcs.api.entity.IEntityLivingBase'),
|
||||
@('World', 'IWorld', 'net.minecraft.world.World', 'noppes.npcs.api.IWorld'),
|
||||
@('NBTTagList', 'INbtList', 'net.minecraft.nbt.NBTTagList', 'noppes.npcs.api.INbtList'),
|
||||
@('DamageSource', 'IDamageSource', 'net.minecraft.util.DamageSource', 'noppes.npcs.api.IDamageSource'),
|
||||
@('WorldServer', 'IWorld', 'net.minecraft.world.WorldServer', 'noppes.npcs.api.IWorld'),
|
||||
@('EntityLiving', 'IEntityLiving', 'net.minecraft.entity.EntityLiving', 'noppes.npcs.api.entity.IEntityLiving'),
|
||||
@('ResourceLocation', 'IResourceLocation', 'net.minecraft.util.ResourceLocation', 'noppes.npcs.api.IResourceLocation'),
|
||||
@('AxisAlignedBB', 'IBoundingBox', 'net.minecraft.util.AxisAlignedBB', 'noppes.npcs.api.IBoundingBox'),
|
||||
@('ICommandSender', 'ICommandSender', 'net.minecraft.command.ICommandSender', 'noppes.npcs.api.ICommandSender'),
|
||||
@('Vec3', 'IVector3', 'net.minecraft.util.Vec3', 'noppes.npcs.api.IVector3'),
|
||||
@('ChatComponentText', 'ITextComponent', 'net.minecraft.util.ChatComponentText', 'noppes.npcs.api.ITextComponent'),
|
||||
@('ChatComponentTranslation', 'ITextComponent', 'net.minecraft.util.ChatComponentTranslation', 'noppes.npcs.api.ITextComponent'),
|
||||
@('IChatComponent', 'ITextComponent', 'net.minecraft.util.IChatComponent', 'noppes.npcs.api.ITextComponent'),
|
||||
@('ChatStyle', 'ITextStyle', 'net.minecraft.util.ChatStyle', 'noppes.npcs.api.ITextStyle'),
|
||||
@('MovingObjectPosition', 'IRayTraceResult', 'net.minecraft.util.MovingObjectPosition', 'noppes.npcs.api.IRayTraceResult'),
|
||||
@('NBTBase', 'INbtBase', 'net.minecraft.nbt.NBTBase', 'noppes.npcs.api.INbtBase'),
|
||||
@('Potion', 'IPotionType', 'net.minecraft.potion.Potion', 'noppes.npcs.api.IPotionType'),
|
||||
@('PotionEffect', 'IPotionEffect', 'net.minecraft.potion.PotionEffect', 'noppes.npcs.api.IPotionEffect'),
|
||||
@('Explosion', 'IExplosion', 'net.minecraft.world.Explosion', 'noppes.npcs.api.IExplosion'),
|
||||
@('Configuration', 'IForgeConfig', 'net.minecraftforge.common.config.Configuration', 'noppes.npcs.api.IForgeConfig'),
|
||||
@('Property', 'IConfigProperty', 'net.minecraftforge.common.config.Property', 'noppes.npcs.api.IConfigProperty')
|
||||
)
|
||||
|
||||
# ============================================================
|
||||
# STEP 4: Static-Transform — regex rewrites
|
||||
# ============================================================
|
||||
Static-Transform -Directory 'core/src/main/java' -Transforms @(
|
||||
@('@SideOnly\s*\(\s*Side\.CLIENT\s*\)', '@ClientOnly'),
|
||||
@('@SideOnly\s*\(\s*Side\.SERVER\s*\)', '@ServerOnly'),
|
||||
@('Side\.CLIENT', 'PlatformSide.CLIENT'),
|
||||
@('Side\.SERVER', 'PlatformSide.SERVER'),
|
||||
@('MathHelper\.clamp_int\s*\(', 'ValueUtil.clampInt('),
|
||||
@('MathHelper\.clamp_float\s*\(', 'ValueUtil.clampFloat('),
|
||||
@('MathHelper\.clamp_double\s*\(', 'ValueUtil.clampDouble('),
|
||||
@('MathHelper\.sqrt_double\s*\(', 'Math.sqrt('),
|
||||
@('MathHelper\.sqrt_float\s*\(', '(float)Math.sqrt('),
|
||||
@('MathHelper\.floor_double\s*\(', 'ValueUtil.floorDouble('),
|
||||
@('MathHelper\.floor_float\s*\(', 'ValueUtil.floorFloat('),
|
||||
@('MathHelper\.sin\s*\(', '(float)Math.sin('),
|
||||
@('MathHelper\.cos\s*\(', '(float)Math.cos('),
|
||||
@('MathHelper\.abs\s*\(', 'Math.abs('),
|
||||
@('MathHelper\.ceiling_double_int\s*\(', 'ValueUtil.ceilToInt('),
|
||||
@('MathHelper\.ceiling_float_int\s*\(', 'ValueUtil.ceilToInt('),
|
||||
@('EnumChatFormatting\.', 'TextFormatting.'),
|
||||
@('EnumFacing\.', 'Direction.'),
|
||||
@('SharedMonsterAttributes\.maxHealth', 'Attributes.MAX_HEALTH'),
|
||||
@('SharedMonsterAttributes\.movementSpeed', 'Attributes.MOVEMENT_SPEED'),
|
||||
@('SharedMonsterAttributes\.attackDamage', 'Attributes.ATTACK_DAMAGE'),
|
||||
@('SharedMonsterAttributes\.followRange', 'Attributes.FOLLOW_RANGE'),
|
||||
@('SharedMonsterAttributes\.knockbackResistance', 'Attributes.KNOCKBACK_RESISTANCE'),
|
||||
@('Constants\.NBT\.TAG_COMPOUND', 'NbtConstants.TAG_COMPOUND'),
|
||||
@('Constants\.NBT\.TAG_STRING', 'NbtConstants.TAG_STRING'),
|
||||
@('Constants\.NBT\.TAG_LIST', 'NbtConstants.TAG_LIST'),
|
||||
@('Constants\.NBT\.TAG_INT', 'NbtConstants.TAG_INT'),
|
||||
@('Constants\.NBT\.TAG_DOUBLE', 'NbtConstants.TAG_DOUBLE'),
|
||||
@('Constants\.NBT\.TAG_BYTE', 'NbtConstants.TAG_BYTE'),
|
||||
@('Constants\.NBT\.TAG_FLOAT', 'NbtConstants.TAG_FLOAT'),
|
||||
@('Constants\.NBT\.TAG_LONG', 'NbtConstants.TAG_LONG'),
|
||||
@('Constants\.NBT\.TAG_SHORT', 'NbtConstants.TAG_SHORT'),
|
||||
@('Constants\.NBT\.TAG_INT_ARRAY', 'NbtConstants.TAG_INT_ARRAY'),
|
||||
@('Constants\.NBT\.TAG_BYTE_ARRAY', 'NbtConstants.TAG_BYTE_ARRAY'),
|
||||
@('net\.minecraft\.command\.CommandException', 'noppes.npcs.api.CommandException'),
|
||||
@('new CommandException\(', 'new CommandException('),
|
||||
@('new ChunkCoordinates\(([^,]+),\s*([^,]+),\s*([^)]+)\)', 'new BlockPos($1, $2, $3)'),
|
||||
@('\.posX\b', '.getX()'),
|
||||
@('\.posY\b', '.getY()'),
|
||||
@('\.posZ\b', '.getZ()'),
|
||||
@('EnumAction\.', 'ItemAction.')
|
||||
)
|
||||
|
||||
# ============================================================
|
||||
# STEP 5: Generate stubs for missing dependencies
|
||||
# ============================================================
|
||||
Generate-Stubs -Stubs @(
|
||||
@{ pkg='noppes.npcs'; name='CustomNpcs'; type='class';
|
||||
body=' public static java.io.File Dir = new java.io.File(".");
|
||||
public static int DefaultInteractLine = 0;' },
|
||||
@{ pkg='noppes.npcs'; name='EventHooks'; type='class';
|
||||
body=' // STUB - event dispatch stays in mc1710' },
|
||||
@{ pkg='noppes.npcs'; name='LogWriter'; type='class';
|
||||
body=' public static void info(String msg) {}
|
||||
public static void warn(String msg) {}
|
||||
public static void error(String msg) {}
|
||||
public static void error(String msg, Throwable e) {}' },
|
||||
@{ pkg='noppes.npcs.controllers'; name='SyncController'; type='class';
|
||||
body=' // STUB - sync stays in mc1710' }
|
||||
)
|
||||
|
||||
# ============================================================
|
||||
# STEP 6: Deduplicate imports
|
||||
# ============================================================
|
||||
Dedupe-Imports -Directory 'core/src/main/java'
|
||||
|
||||
# ============================================================
|
||||
# STEP 7: Build and report
|
||||
# ============================================================
|
||||
Build-Report -GradleTask ':core:compileJava'
|
||||
@@ -16,6 +16,7 @@ Subcommands:
|
||||
coverage-report Migration coverage analysis (P3)
|
||||
classify Classify MC type for migration strategy (P0)
|
||||
classify-all Classify ALL MC types in index and output as JSON (P0)
|
||||
generate-mappings Generate mc_type_mappings.json from curated classifications
|
||||
|
||||
Usage:
|
||||
python analyze.py query-type EntityPlayer
|
||||
@@ -2251,6 +2252,204 @@ def add_migration_diff_args(subparsers):
|
||||
p.set_defaults(func=cmd_migration_diff)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# generate-mappings: Build mc_type_mappings.json from curated classifications
|
||||
# ============================================================
|
||||
|
||||
|
||||
def cmd_generate_mappings(args):
|
||||
"""Read mc_type_classifications_original.json and produce mc_type_mappings.json."""
|
||||
project_root = (
|
||||
Path(args.project_root) if args.project_root else auto_detect_project_root()
|
||||
)
|
||||
|
||||
cls_path = Path(args.classifications)
|
||||
if not cls_path.is_absolute():
|
||||
cls_path = project_root / args.classifications
|
||||
if not cls_path.exists():
|
||||
print(f"ERROR: Classifications file not found: {cls_path}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
with open(cls_path, "r", encoding="utf-8") as f:
|
||||
cls_data = json.load(f)
|
||||
|
||||
types = cls_data.get("types", {})
|
||||
allowed_categories = None
|
||||
if args.category:
|
||||
allowed_categories = set(c.upper() for c in args.category)
|
||||
|
||||
mappings = {}
|
||||
category_counts = defaultdict(int)
|
||||
|
||||
for type_name, info in types.items():
|
||||
if type_name.startswith("__"):
|
||||
continue
|
||||
|
||||
category = info.get("category", "")
|
||||
if not category:
|
||||
continue
|
||||
|
||||
if allowed_categories and category not in allowed_categories:
|
||||
continue
|
||||
|
||||
category_counts[category] += 1
|
||||
imports = info.get("imports", 0)
|
||||
fqn = info.get("fqn", "")
|
||||
|
||||
if category == "EXISTING":
|
||||
pa_name = info.get("pa", "")
|
||||
pa_package = info.get("package", "")
|
||||
pa_fqn = f"{pa_package}.{pa_name}" if pa_package and pa_name else ""
|
||||
entry = {
|
||||
"fqn": fqn,
|
||||
"category": category,
|
||||
"pa_name": pa_name,
|
||||
"pa_package": pa_package,
|
||||
"pa_fqn": pa_fqn,
|
||||
"imports": imports,
|
||||
"swap": [type_name, pa_name, fqn, pa_fqn],
|
||||
}
|
||||
|
||||
elif category == "INTERFACE":
|
||||
pa_name = info.get("pa", "")
|
||||
pa_package = info.get("package", "")
|
||||
pa_fqn = f"{pa_package}.{pa_name}" if pa_package and pa_name else ""
|
||||
entry = {
|
||||
"fqn": fqn,
|
||||
"category": category,
|
||||
"pa_name": pa_name,
|
||||
"pa_package": pa_package,
|
||||
"pa_fqn": pa_fqn,
|
||||
"imports": imports,
|
||||
"swap": [type_name, pa_name, fqn, pa_fqn],
|
||||
}
|
||||
if "methods" in info:
|
||||
entry["methods"] = info["methods"]
|
||||
if "constructors" in info:
|
||||
entry["constructors"] = info["constructors"]
|
||||
|
||||
elif category == "SERVICE":
|
||||
entry = {
|
||||
"fqn": fqn,
|
||||
"category": category,
|
||||
"service_target": info.get("service", ""),
|
||||
"imports": imports,
|
||||
"methods": info.get("methods", {}),
|
||||
"swap": None,
|
||||
}
|
||||
|
||||
elif category == "TRANSFORM":
|
||||
entry = {
|
||||
"fqn": fqn,
|
||||
"category": category,
|
||||
"patterns": info.get("patterns", []),
|
||||
"imports": imports,
|
||||
"swap": None,
|
||||
}
|
||||
|
||||
elif category == "SUPPRESS":
|
||||
entry = {
|
||||
"fqn": fqn,
|
||||
"category": category,
|
||||
"reason": info.get("reason", ""),
|
||||
"imports": imports,
|
||||
"swap": None,
|
||||
}
|
||||
|
||||
else:
|
||||
entry = {
|
||||
"fqn": fqn,
|
||||
"category": category,
|
||||
"imports": imports,
|
||||
"swap": None,
|
||||
}
|
||||
|
||||
mappings[type_name] = entry
|
||||
|
||||
timestamp = datetime.now(timezone.utc).isoformat()
|
||||
total_types = len(mappings)
|
||||
|
||||
cat_order = ["EXISTING", "INTERFACE", "SERVICE", "TRANSFORM", "SUPPRESS"]
|
||||
categories_summary = {}
|
||||
for cat in cat_order:
|
||||
if category_counts.get(cat, 0) > 0:
|
||||
categories_summary[cat] = category_counts[cat]
|
||||
for cat, count in sorted(category_counts.items()):
|
||||
if cat not in categories_summary and count > 0:
|
||||
categories_summary[cat] = count
|
||||
|
||||
output = {
|
||||
"_meta": {
|
||||
"generated": timestamp,
|
||||
"source": cls_path.name,
|
||||
"total_types": total_types,
|
||||
"categories": categories_summary,
|
||||
},
|
||||
"mappings": mappings,
|
||||
}
|
||||
|
||||
if args.json_output:
|
||||
json.dump(output, sys.stdout, indent=2, ensure_ascii=True)
|
||||
print()
|
||||
else:
|
||||
output_path = Path(args.output)
|
||||
if not output_path.is_absolute():
|
||||
output_path = project_root / args.output
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
json.dump(output, f, indent=2, ensure_ascii=False)
|
||||
f.write("\n")
|
||||
|
||||
print()
|
||||
print("=" * 80)
|
||||
print("GENERATE MAPPINGS")
|
||||
print("=" * 80)
|
||||
print(f" Source: {cls_path.name}")
|
||||
print(f" Total types: {total_types}")
|
||||
print()
|
||||
print(" CATEGORY BREAKDOWN:")
|
||||
for cat in cat_order:
|
||||
count = categories_summary.get(cat, 0)
|
||||
if count > 0:
|
||||
print(f" {cat:<14}{count:>4} types")
|
||||
print()
|
||||
print(f" Output written: {output_path}")
|
||||
print("=" * 80)
|
||||
print()
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def add_generate_mappings_args(subparsers):
|
||||
p = subparsers.add_parser(
|
||||
"generate-mappings",
|
||||
help="Generate mc_type_mappings.json from curated classifications",
|
||||
)
|
||||
p.add_argument(
|
||||
"--output",
|
||||
default="tools/migration/mc_type_mappings.json",
|
||||
help="Output JSON file path (default: tools/migration/mc_type_mappings.json)",
|
||||
)
|
||||
p.add_argument(
|
||||
"--classifications",
|
||||
default="tools/migration/mc_type_classifications_original.json",
|
||||
help="Path to source classifications JSON (relative to project root)",
|
||||
)
|
||||
p.add_argument(
|
||||
"--json",
|
||||
dest="json_output",
|
||||
action="store_true",
|
||||
help="Print output to stdout as JSON instead of writing to file",
|
||||
)
|
||||
p.add_argument(
|
||||
"--category",
|
||||
nargs="+",
|
||||
help="Filter to specific categories (e.g. EXISTING INTERFACE)",
|
||||
)
|
||||
p.add_argument("--project-root", help="Project root (auto-detected if omitted)")
|
||||
p.set_defaults(func=cmd_generate_mappings)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Main CLI
|
||||
# ============================================================
|
||||
@@ -2274,6 +2473,7 @@ def main():
|
||||
add_coverage_report_args(subparsers)
|
||||
add_classify_args(subparsers)
|
||||
add_classify_all_args(subparsers)
|
||||
add_generate_mappings_args(subparsers)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
@@ -0,0 +1,488 @@
|
||||
"""
|
||||
batch_migration_diff.py — Batch Migration Diff
|
||||
================================================
|
||||
|
||||
Runs migration-diff logic on ALL migrated files in core/ and produces
|
||||
one unified JSON with every file's verdict (PASS/WARN/FAIL/NO_COUNTERPART).
|
||||
|
||||
Usage:
|
||||
python tools/migration/batch_migration_diff.py
|
||||
python tools/migration/batch_migration_diff.py --json
|
||||
python tools/migration/batch_migration_diff.py --output tools/migration/_batch_diffs.json
|
||||
python tools/migration/batch_migration_diff.py --mc-root src/main/java --core-root core/src/main/java
|
||||
python tools/migration/batch_migration_diff.py --classifications tools/migration/mc_type_classifications_original.json
|
||||
python tools/migration/batch_migration_diff.py --project-root /path/to/project
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import argparse
|
||||
import difflib
|
||||
import re
|
||||
from pathlib import Path
|
||||
from datetime import datetime, timezone
|
||||
|
||||
# Add script directory to path so we can import analyze.py and pa_rules.py
|
||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||
if str(SCRIPT_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(SCRIPT_DIR))
|
||||
|
||||
|
||||
def auto_detect_project_root():
|
||||
"""Auto-detect project root from script location (tools/migration/ -> root)."""
|
||||
return SCRIPT_DIR.parent.parent
|
||||
|
||||
|
||||
def _try_import_analyze():
|
||||
"""Try to import functions from analyze.py. Returns a dict of functions or None."""
|
||||
try:
|
||||
from analyze import (
|
||||
setup_treesitter_parser,
|
||||
_load_classifications,
|
||||
_extract_method_bodies,
|
||||
_normalized_signature_key,
|
||||
normalize_text,
|
||||
_scan_unmigrated_types,
|
||||
)
|
||||
|
||||
return {
|
||||
"setup_treesitter_parser": setup_treesitter_parser,
|
||||
"_load_classifications": _load_classifications,
|
||||
"_extract_method_bodies": _extract_method_bodies,
|
||||
"_normalized_signature_key": _normalized_signature_key,
|
||||
"normalize_text": normalize_text,
|
||||
"_scan_unmigrated_types": _scan_unmigrated_types,
|
||||
}
|
||||
except ImportError as e:
|
||||
print(f"WARNING: Could not import from analyze.py: {e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
def _normalize_classifications(data):
|
||||
"""Normalize both mc_type_mappings.json and mc_type_classifications_original.json formats."""
|
||||
types = data.get("types") or data.get("mappings") or {}
|
||||
normalized = {}
|
||||
for type_name, info in types.items():
|
||||
if type_name.startswith("_"):
|
||||
normalized[type_name] = info
|
||||
continue
|
||||
entry = dict(info)
|
||||
if "pa_name" in entry and "pa" not in entry:
|
||||
entry["pa"] = entry["pa_name"]
|
||||
if "pa_package" in entry and "package" not in entry:
|
||||
entry["package"] = entry["pa_package"]
|
||||
if "service_name" in entry and "service" not in entry:
|
||||
entry["service"] = entry["service_name"]
|
||||
normalized[type_name] = entry
|
||||
return normalized
|
||||
|
||||
|
||||
def _load_classifications_fallback(project_root, classifications_path):
|
||||
cls_file = Path(project_root) / classifications_path
|
||||
if not cls_file.exists():
|
||||
raise FileNotFoundError(f"Classifications file not found: {cls_file}")
|
||||
with open(cls_file, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
return _normalize_classifications(data)
|
||||
|
||||
|
||||
def _scan_unmigrated_types_fallback(file_text, classifications):
|
||||
"""Scan file text for MC type names that should have been replaced. Standalone fallback."""
|
||||
results = []
|
||||
lines = file_text.split("\n")
|
||||
for type_name, info in classifications.items():
|
||||
if type_name.startswith("_"):
|
||||
continue
|
||||
category = info.get("category", "")
|
||||
if category not in ("EXISTING", "INTERFACE", "SERVICE"):
|
||||
continue
|
||||
pa_name = info.get("pa") or info.get("service")
|
||||
if not pa_name:
|
||||
continue
|
||||
if len(type_name) < 4:
|
||||
continue
|
||||
pattern = re.compile(r"\b" + re.escape(type_name) + r"\b")
|
||||
found_lines = []
|
||||
has_import = False
|
||||
for i, line in enumerate(lines, 1):
|
||||
if pattern.search(line):
|
||||
if line.strip().startswith("import "):
|
||||
has_import = True
|
||||
found_lines.append(i)
|
||||
if found_lines:
|
||||
results.append(
|
||||
{
|
||||
"mc_name": type_name,
|
||||
"pa_name": pa_name,
|
||||
"lines": found_lines,
|
||||
"in_import": has_import,
|
||||
}
|
||||
)
|
||||
return results
|
||||
|
||||
|
||||
def _resolve_classifications_path(args):
|
||||
"""Resolve which classifications file to use."""
|
||||
if args.classifications:
|
||||
return args.classifications
|
||||
# Prefer mc_type_mappings.json if it exists
|
||||
project_root = (
|
||||
Path(args.project_root) if args.project_root else auto_detect_project_root()
|
||||
)
|
||||
mappings_path = project_root / "tools" / "migration" / "mc_type_mappings.json"
|
||||
if mappings_path.exists():
|
||||
return "tools/migration/mc_type_mappings.json"
|
||||
# Fallback to original classifications
|
||||
original_path = (
|
||||
project_root / "tools" / "migration" / "mc_type_classifications_original.json"
|
||||
)
|
||||
if original_path.exists():
|
||||
return "tools/migration/mc_type_classifications_original.json"
|
||||
# Default
|
||||
return "tools/migration/mc_type_classifications.json"
|
||||
|
||||
|
||||
def run_single_diff(old_file, new_file, parser, classifications, analyze_fns):
|
||||
"""
|
||||
Run migration-diff logic on a single file pair.
|
||||
Returns a result dict with verdict, unmigrated, missing_methods, semantic_body_diffs.
|
||||
"""
|
||||
result = {
|
||||
"unmigrated": [],
|
||||
"missing_methods": [],
|
||||
"semantic_body_diffs": [],
|
||||
}
|
||||
|
||||
# Step 1: Scan for unmigrated types in new file
|
||||
new_text = new_file.read_text(encoding="utf-8", errors="replace")
|
||||
if analyze_fns:
|
||||
unmigrated = analyze_fns["_scan_unmigrated_types"](new_text, classifications)
|
||||
else:
|
||||
unmigrated = _scan_unmigrated_types_fallback(new_text, classifications)
|
||||
result["unmigrated"] = unmigrated
|
||||
|
||||
# Step 2 & 3: Method-level analysis (requires tree-sitter)
|
||||
if parser is not None and analyze_fns:
|
||||
try:
|
||||
_extract = analyze_fns["_extract_method_bodies"]
|
||||
_norm_key = analyze_fns["_normalized_signature_key"]
|
||||
_norm_text = analyze_fns["normalize_text"]
|
||||
|
||||
old_methods, _ = _extract(old_file, parser)
|
||||
new_methods, _ = _extract(new_file, parser)
|
||||
|
||||
# Build maps by normalized key
|
||||
old_method_map = {}
|
||||
for m in old_methods:
|
||||
key = _norm_key(m, classifications)
|
||||
old_method_map[key] = m
|
||||
|
||||
new_method_map = {}
|
||||
for m in new_methods:
|
||||
key = _norm_key(m, classifications)
|
||||
new_method_map[key] = m
|
||||
|
||||
old_keys = set(old_method_map.keys())
|
||||
new_keys = set(new_method_map.keys())
|
||||
|
||||
# Missing methods
|
||||
for key in sorted(old_keys - new_keys):
|
||||
m = old_method_map[key]
|
||||
result["missing_methods"].append(
|
||||
{
|
||||
"name": m["name"],
|
||||
"signature": m["signature_text"],
|
||||
"line": m["line"],
|
||||
}
|
||||
)
|
||||
|
||||
# Semantic body diffs
|
||||
common_keys = old_keys & new_keys
|
||||
for key in sorted(common_keys):
|
||||
old_m = old_method_map[key]
|
||||
new_m = new_method_map[key]
|
||||
|
||||
old_body = old_m.get("body")
|
||||
new_body = new_m.get("body")
|
||||
if old_body is None or new_body is None:
|
||||
continue
|
||||
|
||||
norm_old = _norm_text(old_body, classifications)
|
||||
norm_new = _norm_text(new_body, classifications)
|
||||
|
||||
norm_old_lines = norm_old.strip().splitlines()
|
||||
norm_new_lines = norm_new.strip().splitlines()
|
||||
|
||||
matcher = difflib.SequenceMatcher(None, norm_old_lines, norm_new_lines)
|
||||
ratio = matcher.ratio()
|
||||
|
||||
diff_lines = list(
|
||||
difflib.unified_diff(
|
||||
norm_old_lines,
|
||||
norm_new_lines,
|
||||
fromfile="old (normalized)",
|
||||
tofile="new (normalized)",
|
||||
lineterm="",
|
||||
)
|
||||
)
|
||||
change_lines = [
|
||||
l
|
||||
for l in diff_lines
|
||||
if (l.startswith("+") or l.startswith("-"))
|
||||
and not l.startswith("+++")
|
||||
and not l.startswith("---")
|
||||
]
|
||||
|
||||
if ratio < 0.85 or len(change_lines) > 0:
|
||||
result["semantic_body_diffs"].append(
|
||||
{
|
||||
"name": old_m["name"],
|
||||
"signature": old_m["signature_text"],
|
||||
"similarity_ratio": round(ratio, 3),
|
||||
"diff_line_count": len(change_lines),
|
||||
"diff_preview": diff_lines[:10],
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
result["note"] = f"tree-sitter analysis error: {str(e)}"
|
||||
else:
|
||||
result["note"] = "tree-sitter not available"
|
||||
|
||||
# Verdict
|
||||
if result["missing_methods"]:
|
||||
result["verdict"] = "FAIL"
|
||||
result["verdict_reason"] = f"{len(result['missing_methods'])} missing method(s)"
|
||||
elif result["unmigrated"] or result["semantic_body_diffs"]:
|
||||
parts = []
|
||||
if result["unmigrated"]:
|
||||
parts.append(f"{len(result['unmigrated'])} unmigrated type(s)")
|
||||
if result["semantic_body_diffs"]:
|
||||
parts.append(f"{len(result['semantic_body_diffs'])} body diff(s)")
|
||||
result["verdict"] = "WARN"
|
||||
result["verdict_reason"] = ", ".join(parts)
|
||||
else:
|
||||
result["verdict"] = "PASS"
|
||||
result["verdict_reason"] = (
|
||||
"all methods present, no unmigrated types, no body drift"
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Batch migration-diff: run migration-diff on ALL core/ files"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--mc-root",
|
||||
default="src/main/java",
|
||||
help="MC 1.7.10 source root (default: src/main/java)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--core-root",
|
||||
default="core/src/main/java",
|
||||
help="Core source root (default: core/src/main/java)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--classifications",
|
||||
help="Path to classifications JSON (relative to project root)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output",
|
||||
default="tools/migration/_batch_diffs.json",
|
||||
help="Output JSON file path (relative to project root)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--json",
|
||||
dest="json_output",
|
||||
action="store_true",
|
||||
help="Print JSON to stdout instead of human-readable output",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--project-root",
|
||||
help="Project root (auto-detected if omitted)",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
project_root = (
|
||||
Path(args.project_root) if args.project_root else auto_detect_project_root()
|
||||
)
|
||||
|
||||
# Resolve classifications path
|
||||
classifications_path = _resolve_classifications_path(args)
|
||||
|
||||
# Try to import from analyze.py
|
||||
analyze_fns = _try_import_analyze()
|
||||
|
||||
# Load classifications
|
||||
if analyze_fns:
|
||||
try:
|
||||
classifications = analyze_fns["_load_classifications"](
|
||||
project_root, classifications_path
|
||||
)
|
||||
if not classifications:
|
||||
classifications = _load_classifications_fallback(
|
||||
project_root, classifications_path
|
||||
)
|
||||
except (FileNotFoundError, KeyError):
|
||||
classifications = _load_classifications_fallback(
|
||||
project_root, classifications_path
|
||||
)
|
||||
else:
|
||||
classifications = _load_classifications_fallback(
|
||||
project_root, classifications_path
|
||||
)
|
||||
|
||||
# Setup tree-sitter parser
|
||||
ts_parser = None
|
||||
if analyze_fns:
|
||||
try:
|
||||
ts_parser, _ = analyze_fns["setup_treesitter_parser"]()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Enumerate all .java files in core-root
|
||||
core_root = project_root / args.core_root
|
||||
mc_root = project_root / args.mc_root
|
||||
|
||||
if not core_root.exists():
|
||||
print(f"ERROR: Core root not found: {core_root}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
core_files = sorted(core_root.rglob("*.java"))
|
||||
total_files = len(core_files)
|
||||
|
||||
results = []
|
||||
summary = {"PASS": [], "WARN": [], "FAIL": [], "NO_COUNTERPART": []}
|
||||
files_with_counterpart = 0
|
||||
files_without_counterpart = 0
|
||||
|
||||
for core_file in core_files:
|
||||
rel_path = core_file.relative_to(core_root)
|
||||
# Normalize to forward slashes for consistency
|
||||
rel_path_str = str(rel_path).replace("\\", "/")
|
||||
|
||||
mc_file = mc_root / rel_path
|
||||
|
||||
if mc_file.exists():
|
||||
files_with_counterpart += 1
|
||||
diff_result = run_single_diff(
|
||||
mc_file, core_file, ts_parser, classifications, analyze_fns
|
||||
)
|
||||
|
||||
entry = {
|
||||
"relative_path": rel_path_str,
|
||||
"old_file": str(Path(args.mc_root) / rel_path).replace("\\", "/"),
|
||||
"new_file": str(Path(args.core_root) / rel_path).replace("\\", "/"),
|
||||
"verdict": diff_result["verdict"],
|
||||
"verdict_reason": diff_result["verdict_reason"],
|
||||
"unmigrated_count": len(diff_result["unmigrated"]),
|
||||
"missing_methods_count": len(diff_result["missing_methods"]),
|
||||
"semantic_diffs_count": len(diff_result["semantic_body_diffs"]),
|
||||
"unmigrated": diff_result["unmigrated"],
|
||||
"missing_methods": diff_result["missing_methods"],
|
||||
"semantic_body_diffs": diff_result["semantic_body_diffs"],
|
||||
}
|
||||
if "note" in diff_result:
|
||||
entry["note"] = diff_result["note"]
|
||||
|
||||
results.append(entry)
|
||||
summary[diff_result["verdict"]].append(rel_path_str)
|
||||
else:
|
||||
files_without_counterpart += 1
|
||||
entry = {
|
||||
"relative_path": rel_path_str,
|
||||
"verdict": "NO_COUNTERPART",
|
||||
"verdict_reason": "No mc1710 original found",
|
||||
"new_file": str(Path(args.core_root) / rel_path).replace("\\", "/"),
|
||||
}
|
||||
results.append(entry)
|
||||
summary["NO_COUNTERPART"].append(rel_path_str)
|
||||
|
||||
# Build output
|
||||
output = {
|
||||
"_meta": {
|
||||
"generated": datetime.now(timezone.utc).isoformat(),
|
||||
"mc_root": args.mc_root,
|
||||
"core_root": args.core_root,
|
||||
"classifications_source": classifications_path,
|
||||
"total_files_in_core": total_files,
|
||||
"files_with_counterpart": files_with_counterpart,
|
||||
"files_without_counterpart": files_without_counterpart,
|
||||
"passed": len(summary["PASS"]),
|
||||
"warned": len(summary["WARN"]),
|
||||
"failed": len(summary["FAIL"]),
|
||||
},
|
||||
"results": results,
|
||||
"summary": summary,
|
||||
}
|
||||
|
||||
if args.json_output:
|
||||
json.dump(output, sys.stdout, indent=2)
|
||||
print()
|
||||
return 0
|
||||
|
||||
# Human-readable output
|
||||
print()
|
||||
print(f"Batch Migration Diff — {total_files} core files analyzed")
|
||||
print(f" With counterpart: {files_with_counterpart}")
|
||||
print(f" Without counterpart: {files_without_counterpart}")
|
||||
print()
|
||||
print(f" PASS: {len(summary['PASS']):>4}")
|
||||
print(f" WARN: {len(summary['WARN']):>4}")
|
||||
print(f" FAIL: {len(summary['FAIL']):>4}")
|
||||
print()
|
||||
|
||||
if summary["FAIL"]:
|
||||
print("FAILED FILES:")
|
||||
for rel in summary["FAIL"]:
|
||||
entry = next(r for r in results if r["relative_path"] == rel)
|
||||
print(f" {rel}")
|
||||
if entry.get("missing_methods"):
|
||||
methods = ", ".join(m["name"] for m in entry["missing_methods"])
|
||||
print(
|
||||
f" - {len(entry['missing_methods'])} missing methods: {methods}"
|
||||
)
|
||||
if entry.get("unmigrated"):
|
||||
types = ", ".join(u["mc_name"] for u in entry["unmigrated"])
|
||||
print(f" - {len(entry['unmigrated'])} unmigrated types: {types}")
|
||||
print()
|
||||
|
||||
if summary["WARN"]:
|
||||
shown = min(5, len(summary["WARN"]))
|
||||
print(f"WARNED FILES (showing first {shown}):")
|
||||
for rel in summary["WARN"][:shown]:
|
||||
entry = next(r for r in results if r["relative_path"] == rel)
|
||||
parts = []
|
||||
if entry.get("semantic_body_diffs"):
|
||||
sims = [
|
||||
str(round(d["similarity_ratio"] * 100)) + "%"
|
||||
for d in entry["semantic_body_diffs"]
|
||||
]
|
||||
parts.append(
|
||||
f"{len(entry['semantic_body_diffs'])} body diffs (similarity: {', '.join(sims)})"
|
||||
)
|
||||
if entry.get("unmigrated"):
|
||||
parts.append(f"{len(entry['unmigrated'])} unmigrated types")
|
||||
print(f" {rel}")
|
||||
for part in parts:
|
||||
print(f" ~ {part}")
|
||||
if len(summary["WARN"]) > shown:
|
||||
print(f" ... and {len(summary['WARN']) - shown} more")
|
||||
print()
|
||||
|
||||
# Write JSON output file
|
||||
output_path = project_root / args.output
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
json.dump(output, f, indent=2)
|
||||
print(f"Results written to: {args.output}")
|
||||
print()
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,385 @@
|
||||
"""
|
||||
generate_pa_manifest.py — Generate PA Interface Manifest + Swap Table
|
||||
=====================================================================
|
||||
|
||||
Generates a pa_batch.py-compatible manifest JSON for creating PA interface
|
||||
files in platform-api/, plus a swap table for Symbol-Swap.
|
||||
|
||||
Usage:
|
||||
python tools/migration/generate_pa_manifest.py
|
||||
python tools/migration/generate_pa_manifest.py --category INTERFACE
|
||||
python tools/migration/generate_pa_manifest.py --category INTERFACE EXISTING
|
||||
python tools/migration/generate_pa_manifest.py --output tools/migration/_pa_manifest.json
|
||||
python tools/migration/generate_pa_manifest.py --swap-output tools/migration/_swap_table.json
|
||||
python tools/migration/generate_pa_manifest.py --json
|
||||
python tools/migration/generate_pa_manifest.py --dry-run
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from datetime import datetime, timezone
|
||||
|
||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||
if str(SCRIPT_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(SCRIPT_DIR))
|
||||
|
||||
from pa_rules import (
|
||||
resolve_param_type,
|
||||
should_exclude_method,
|
||||
resolve_obfuscated_method,
|
||||
get_interface_package,
|
||||
NAMING,
|
||||
EXISTING_MAPPINGS,
|
||||
TRANSFORM_PATTERNS,
|
||||
SERVICE_ROUTING,
|
||||
)
|
||||
|
||||
|
||||
def auto_detect_project_root():
|
||||
return SCRIPT_DIR.parent.parent
|
||||
|
||||
|
||||
def _normalize_classifications(data):
|
||||
"""Normalize both mc_type_mappings.json and mc_type_classifications_original.json formats."""
|
||||
types = data.get("types") or data.get("mappings") or {}
|
||||
normalized = {}
|
||||
for type_name, info in types.items():
|
||||
if type_name.startswith("_"):
|
||||
normalized[type_name] = info
|
||||
continue
|
||||
entry = dict(info)
|
||||
if "pa_name" in entry and "pa" not in entry:
|
||||
entry["pa"] = entry["pa_name"]
|
||||
if "pa_package" in entry and "package" not in entry:
|
||||
entry["package"] = entry["pa_package"]
|
||||
if "service_name" in entry and "service" not in entry:
|
||||
entry["service"] = entry["service_name"]
|
||||
normalized[type_name] = entry
|
||||
return normalized
|
||||
|
||||
|
||||
def _load_classifications_data(project_root, mappings_path=None):
|
||||
if mappings_path:
|
||||
p = Path(mappings_path)
|
||||
if not p.is_absolute():
|
||||
p = project_root / mappings_path
|
||||
if p.exists():
|
||||
with open(p, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
return _normalize_classifications(data), str(p.name)
|
||||
for candidate in [
|
||||
"tools/migration/mc_type_mappings.json",
|
||||
"tools/migration/mc_type_classifications_original.json",
|
||||
]:
|
||||
p = project_root / candidate
|
||||
if p.exists():
|
||||
with open(p, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
return _normalize_classifications(data), candidate
|
||||
raise FileNotFoundError("No classifications file found")
|
||||
|
||||
|
||||
def _load_methods_export(project_root, methods_path=None):
|
||||
"""Load all_methods_export.json for usage counts."""
|
||||
if methods_path:
|
||||
p = Path(methods_path)
|
||||
if not p.is_absolute():
|
||||
p = project_root / methods_path
|
||||
else:
|
||||
p = project_root / "tools" / "migration" / "all_methods_export.json"
|
||||
if not p.exists():
|
||||
return {}
|
||||
with open(p, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
lookup = {}
|
||||
for t in data.get("types", []):
|
||||
lookup[t["shortName"]] = t
|
||||
return lookup
|
||||
|
||||
|
||||
def _infer_return_type(method_name):
|
||||
"""Infer return type from Java naming conventions."""
|
||||
if method_name.startswith("get"):
|
||||
return "Object"
|
||||
if method_name.startswith("is") or method_name.startswith("has"):
|
||||
return "boolean"
|
||||
if (
|
||||
method_name.startswith("set")
|
||||
or method_name.startswith("add")
|
||||
or method_name.startswith("remove")
|
||||
):
|
||||
return "void"
|
||||
return "void"
|
||||
|
||||
|
||||
def _get_usage_count(method_name, methods_export_entry):
|
||||
"""Look up usage count for a method from the export data."""
|
||||
if not methods_export_entry:
|
||||
return 0
|
||||
for m in methods_export_entry.get("methods", []):
|
||||
if m["name"] == method_name:
|
||||
return m["count"]
|
||||
for m in methods_export_entry.get("static_methods", []):
|
||||
if m["name"] == method_name:
|
||||
return m["count"]
|
||||
return 0
|
||||
|
||||
|
||||
def _generate_interface_content(type_name, info, methods_export_entry):
|
||||
"""Generate Java interface source code for a PA interface."""
|
||||
pa_name = info.get("pa", NAMING.get(type_name, f"I{type_name}"))
|
||||
fqn = info.get("fqn", "")
|
||||
pkg = info.get("package", get_interface_package(pa_name))
|
||||
|
||||
raw_methods = info.get("methods", [])
|
||||
|
||||
filtered_methods = []
|
||||
for m in raw_methods:
|
||||
method_name = m if isinstance(m, str) else m.get("name", m)
|
||||
if should_exclude_method(method_name):
|
||||
continue
|
||||
resolved_name = resolve_obfuscated_method(method_name)
|
||||
if resolved_name != method_name and should_exclude_method(resolved_name):
|
||||
continue
|
||||
filtered_methods.append(resolved_name)
|
||||
|
||||
seen = set()
|
||||
unique_methods = []
|
||||
for m in filtered_methods:
|
||||
if m not in seen:
|
||||
seen.add(m)
|
||||
unique_methods.append(m)
|
||||
|
||||
lines = []
|
||||
lines.append(f"package {pkg};")
|
||||
lines.append("")
|
||||
lines.append("/**")
|
||||
lines.append(f" * Platform abstraction for {fqn}.")
|
||||
lines.append(f" * MC 1.7.10: {type_name}")
|
||||
lines.append(f" * Generated by generate_pa_manifest.py")
|
||||
lines.append(" */")
|
||||
lines.append(f"public interface {pa_name} {{")
|
||||
|
||||
for method_name in unique_methods:
|
||||
usage = _get_usage_count(method_name, methods_export_entry)
|
||||
ret_type = _infer_return_type(method_name)
|
||||
usage_note = f" — used {usage} times" if usage > 0 else ""
|
||||
lines.append(f" /** {method_name}{usage_note} */")
|
||||
lines.append(f" {ret_type} {method_name}();")
|
||||
|
||||
lines.append("}")
|
||||
lines.append("")
|
||||
|
||||
return "\n".join(lines), pa_name, pkg
|
||||
|
||||
|
||||
def _build_swap_entry(type_name, info):
|
||||
"""Build a swap tuple [old_name, new_name, old_fqn, new_fqn]."""
|
||||
pa_name = info.get("pa", NAMING.get(type_name, f"I{type_name}"))
|
||||
fqn = info.get("fqn", "")
|
||||
pkg = info.get("package", get_interface_package(pa_name))
|
||||
new_fqn = f"{pkg}.{pa_name}"
|
||||
return [type_name, pa_name, fqn, new_fqn]
|
||||
|
||||
|
||||
def _build_transform_entries(classifications):
|
||||
"""Build transform entries from TRANSFORM-category types."""
|
||||
transforms = []
|
||||
for type_name, info in classifications.items():
|
||||
if type_name.startswith("_"):
|
||||
continue
|
||||
if info.get("category") != "TRANSFORM":
|
||||
continue
|
||||
patterns = info.get("patterns", [])
|
||||
for pat in patterns:
|
||||
if len(pat) >= 2:
|
||||
transforms.append(pat)
|
||||
|
||||
for svc_name, routing in SERVICE_ROUTING.items():
|
||||
if routing.get("service") == "NBTIO":
|
||||
pass
|
||||
|
||||
return transforms
|
||||
|
||||
|
||||
def _format_powershell_swaps(swaps):
|
||||
"""Format swaps as a PowerShell array string."""
|
||||
lines = ["$AllSwaps = @("]
|
||||
for i, swap in enumerate(swaps):
|
||||
comma = "," if i < len(swaps) - 1 else ""
|
||||
lines.append(
|
||||
f" @('{swap[0]}', '{swap[1]}', '{swap[2]}', '{swap[3]}'){comma}"
|
||||
)
|
||||
lines.append(")")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate PA interface manifest and swap table"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--mappings",
|
||||
help="Path to mc_type_mappings.json or classifications JSON",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--methods",
|
||||
default=None,
|
||||
help="Path to all_methods_export.json",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--category",
|
||||
nargs="+",
|
||||
default=["INTERFACE"],
|
||||
help="Categories to include (default: INTERFACE)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output",
|
||||
default="tools/migration/_pa_manifest.json",
|
||||
help="Output manifest JSON path (relative to project root)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--swap-output",
|
||||
default="tools/migration/_swap_table.json",
|
||||
help="Output swap table JSON path (relative to project root)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--json",
|
||||
dest="json_output",
|
||||
action="store_true",
|
||||
help="Print manifest to stdout as JSON",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Show what would be generated without writing files",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--project-root",
|
||||
help="Project root (auto-detected if omitted)",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
project_root = (
|
||||
Path(args.project_root) if args.project_root else auto_detect_project_root()
|
||||
)
|
||||
requested_categories = set(c.upper() for c in args.category)
|
||||
|
||||
types, cls_source = _load_classifications_data(project_root, args.mappings)
|
||||
|
||||
methods_export = _load_methods_export(project_root, args.methods)
|
||||
|
||||
manifest_files = []
|
||||
all_swaps = []
|
||||
generated_count = 0
|
||||
|
||||
for type_name, info in types.items():
|
||||
if type_name.startswith("_"):
|
||||
continue
|
||||
category = info.get("category", "")
|
||||
if category not in requested_categories:
|
||||
continue
|
||||
|
||||
pa_name = info.get("pa") or info.get("service")
|
||||
if not pa_name:
|
||||
pa_name = NAMING.get(type_name, f"I{type_name}")
|
||||
|
||||
if category in ("EXISTING", "INTERFACE"):
|
||||
swap = _build_swap_entry(type_name, info)
|
||||
if swap not in all_swaps:
|
||||
all_swaps.append(swap)
|
||||
|
||||
if category == "INTERFACE" and info.get("methods"):
|
||||
export_entry = methods_export.get(type_name)
|
||||
content, gen_pa_name, pkg = _generate_interface_content(
|
||||
type_name, info, export_entry
|
||||
)
|
||||
manifest_files.append({"content": content})
|
||||
generated_count += 1
|
||||
|
||||
manifest = {
|
||||
"root": "platform-api/src/main/java",
|
||||
"files": manifest_files,
|
||||
}
|
||||
|
||||
all_transforms = _build_transform_entries(types)
|
||||
|
||||
swap_table = {
|
||||
"_meta": {
|
||||
"generated": datetime.now(timezone.utc).isoformat(),
|
||||
"source": cls_source,
|
||||
"total_swaps": len(all_swaps),
|
||||
"total_transforms": len(all_transforms),
|
||||
},
|
||||
"swaps": all_swaps,
|
||||
"transforms": all_transforms,
|
||||
"powershell_swaps": _format_powershell_swaps(all_swaps),
|
||||
}
|
||||
|
||||
if args.json_output:
|
||||
json.dump(manifest, sys.stdout, indent=2)
|
||||
print()
|
||||
return 0
|
||||
|
||||
if args.dry_run:
|
||||
print()
|
||||
print(f"PA Manifest Generator — DRY RUN")
|
||||
print(f" Source: {cls_source}")
|
||||
print(f" Categories: {', '.join(sorted(requested_categories))}")
|
||||
print(f" Interfaces to generate: {generated_count}")
|
||||
print(f" Swap entries: {len(all_swaps)}")
|
||||
print(f" Transform entries: {len(all_transforms)}")
|
||||
print()
|
||||
if manifest_files:
|
||||
print("INTERFACES:")
|
||||
for f in manifest_files:
|
||||
content = f["content"]
|
||||
first_line = next(
|
||||
(
|
||||
l
|
||||
for l in content.split("\n")
|
||||
if l.startswith("public interface")
|
||||
),
|
||||
"?",
|
||||
)
|
||||
pkg_line = next(
|
||||
(l for l in content.split("\n") if l.startswith("package ")), "?"
|
||||
)
|
||||
print(f" {pkg_line.rstrip(';')} / {first_line.rstrip(' {')}")
|
||||
print()
|
||||
if all_swaps:
|
||||
print("SWAPS:")
|
||||
for s in all_swaps[:10]:
|
||||
print(f" {s[0]:30s} -> {s[1]:20s} ({s[2]} -> {s[3]})")
|
||||
if len(all_swaps) > 10:
|
||||
print(f" ... and {len(all_swaps) - 10} more")
|
||||
print()
|
||||
return 0
|
||||
|
||||
manifest_path = project_root / args.output
|
||||
manifest_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(manifest_path, "w", encoding="utf-8") as f:
|
||||
json.dump(manifest, f, indent=2)
|
||||
|
||||
swap_path = project_root / args.swap_output
|
||||
swap_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(swap_path, "w", encoding="utf-8") as f:
|
||||
json.dump(swap_table, f, indent=2)
|
||||
|
||||
print()
|
||||
print(f"PA Manifest Generator — {generated_count} interfaces generated")
|
||||
print(f" Source: {cls_source}")
|
||||
print(f" Categories: {', '.join(sorted(requested_categories))}")
|
||||
print(f" Manifest written to: {args.output}")
|
||||
print(f" Swap table written to: {args.swap_output}")
|
||||
print(f" Swap entries: {len(all_swaps)}")
|
||||
print(f" Transform entries: {len(all_transforms)}")
|
||||
print()
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,416 @@
|
||||
"""
|
||||
generate_wave_script.py — Generate a complete .ps1 migration wave script
|
||||
========================================================================
|
||||
|
||||
Reads mc_type_classifications_original.json (or mc_type_mappings.json) and
|
||||
generates a ready-to-run PowerShell migration wave script.
|
||||
|
||||
Usage:
|
||||
python tools/migration/generate_wave_script.py
|
||||
python tools/migration/generate_wave_script.py --sources noppes/npcs/controllers/QuestController.java
|
||||
python tools/migration/generate_wave_script.py --sources-file tools/migration/_wave_sources.txt
|
||||
python tools/migration/generate_wave_script.py --allow-paths "noppes/npcs/controllers/,noppes/npcs/quests/"
|
||||
python tools/migration/generate_wave_script.py --output tools/migration/_wave.ps1
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from datetime import datetime, timezone
|
||||
|
||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||
if str(SCRIPT_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(SCRIPT_DIR))
|
||||
|
||||
from pa_rules import (
|
||||
NAMING,
|
||||
EXISTING_MAPPINGS,
|
||||
get_interface_package,
|
||||
SERVICE_ROUTING,
|
||||
)
|
||||
|
||||
STANDARD_FORBIDDEN_PREFIXES = [
|
||||
"net.minecraft.",
|
||||
"cpw.mods.",
|
||||
"net.minecraftforge.",
|
||||
"noppes.npcs.entity.",
|
||||
"noppes.npcs.client.",
|
||||
"noppes.npcs.mixin.",
|
||||
"kamkeel.npcs.network.",
|
||||
"kamkeel.npcs.entity.",
|
||||
"noppes.npcs.containers.",
|
||||
"noppes.npcs.scripted.",
|
||||
"noppes.npcs.janino.",
|
||||
"noppes.npcs.wrapper.",
|
||||
"noppes.npcs.blocks.",
|
||||
"noppes.npcs.items.",
|
||||
"noppes.npcs.ai.",
|
||||
]
|
||||
|
||||
DEFAULT_STUBS = [
|
||||
{
|
||||
"pkg": "noppes.npcs",
|
||||
"name": "CustomNpcs",
|
||||
"type": "class",
|
||||
"body": ' public static java.io.File Dir = new java.io.File(".");\n public static int DefaultInteractLine = 0;',
|
||||
},
|
||||
{
|
||||
"pkg": "noppes.npcs",
|
||||
"name": "EventHooks",
|
||||
"type": "class",
|
||||
"body": " // STUB - event dispatch stays in mc1710",
|
||||
},
|
||||
{
|
||||
"pkg": "noppes.npcs",
|
||||
"name": "LogWriter",
|
||||
"type": "class",
|
||||
"body": " public static void info(String msg) {}\n public static void warn(String msg) {}\n public static void error(String msg) {}\n public static void error(String msg, Throwable e) {}",
|
||||
},
|
||||
{
|
||||
"pkg": "noppes.npcs.controllers",
|
||||
"name": "SyncController",
|
||||
"type": "class",
|
||||
"body": " // STUB - sync stays in mc1710",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def auto_detect_project_root():
|
||||
return SCRIPT_DIR.parent.parent
|
||||
|
||||
|
||||
def _normalize_classifications(data):
|
||||
types = data.get("types") or data.get("mappings") or {}
|
||||
normalized = {}
|
||||
for type_name, info in types.items():
|
||||
if type_name.startswith("_"):
|
||||
normalized[type_name] = info
|
||||
continue
|
||||
entry = dict(info)
|
||||
if "pa_name" in entry and "pa" not in entry:
|
||||
entry["pa"] = entry["pa_name"]
|
||||
if "pa_package" in entry and "package" not in entry:
|
||||
entry["package"] = entry["pa_package"]
|
||||
if "service_name" in entry and "service" not in entry:
|
||||
entry["service"] = entry["service_name"]
|
||||
normalized[type_name] = entry
|
||||
return normalized
|
||||
|
||||
|
||||
def _load_classifications_data(project_root, mappings_path=None):
|
||||
if mappings_path:
|
||||
p = Path(mappings_path)
|
||||
if not p.is_absolute():
|
||||
p = project_root / mappings_path
|
||||
if p.exists():
|
||||
with open(p, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
return _normalize_classifications(data), str(p.name)
|
||||
for candidate in [
|
||||
"tools/migration/mc_type_mappings.json",
|
||||
"tools/migration/mc_type_classifications_original.json",
|
||||
]:
|
||||
p = project_root / candidate
|
||||
if p.exists():
|
||||
with open(p, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
return _normalize_classifications(data), candidate
|
||||
raise FileNotFoundError("No classifications file found")
|
||||
|
||||
|
||||
def _build_all_swaps(types):
|
||||
"""Build swap entries from EXISTING + INTERFACE categories."""
|
||||
swaps = []
|
||||
seen = set()
|
||||
for type_name, info in types.items():
|
||||
if type_name.startswith("_"):
|
||||
continue
|
||||
category = info.get("category", "")
|
||||
if category not in ("EXISTING", "INTERFACE"):
|
||||
continue
|
||||
pa_name = info.get("pa")
|
||||
if not pa_name:
|
||||
pa_name = NAMING.get(type_name, f"I{type_name}")
|
||||
fqn = info.get("fqn", "")
|
||||
pkg = info.get("package", get_interface_package(pa_name))
|
||||
new_fqn = f"{pkg}.{pa_name}"
|
||||
key = (type_name, fqn)
|
||||
if key not in seen:
|
||||
seen.add(key)
|
||||
swaps.append((type_name, pa_name, fqn, new_fqn))
|
||||
return swaps
|
||||
|
||||
|
||||
def _build_all_transforms(types):
|
||||
"""Build transform entries from TRANSFORM category + SERVICE static-transform patterns."""
|
||||
transforms = []
|
||||
for type_name, info in types.items():
|
||||
if type_name.startswith("_"):
|
||||
continue
|
||||
if info.get("category") != "TRANSFORM":
|
||||
continue
|
||||
for pat in info.get("patterns", []):
|
||||
if len(pat) >= 2:
|
||||
transforms.append((pat[0], pat[1]))
|
||||
return transforms
|
||||
|
||||
|
||||
def _ps_array_literal(items, indent=" "):
|
||||
"""Format a list of strings as a PowerShell @() array literal."""
|
||||
if not items:
|
||||
return "@()"
|
||||
lines = ["@("]
|
||||
for i, item in enumerate(items):
|
||||
comma = "," if i < len(items) - 1 else ""
|
||||
lines.append(f"{indent} '{item}'{comma}")
|
||||
lines.append(f"{indent})")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _ps_swap_array(swaps, indent=" "):
|
||||
"""Format swap entries as a PowerShell @(@(...), @(...)) array."""
|
||||
if not swaps:
|
||||
return "@()"
|
||||
lines = ["@("]
|
||||
for i, (old, new, old_fqn, new_fqn) in enumerate(swaps):
|
||||
comma = "," if i < len(swaps) - 1 else ""
|
||||
lines.append(
|
||||
f"{indent} @('{old}', '{new}', '{old_fqn}', '{new_fqn}'){comma}"
|
||||
)
|
||||
lines.append(f"{indent})")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _ps_transform_array(transforms, indent=" "):
|
||||
"""Format transform entries as a PowerShell @(@(...), @(...)) array."""
|
||||
if not transforms:
|
||||
return "@()"
|
||||
lines = ["@("]
|
||||
for i, (pattern, replacement) in enumerate(transforms):
|
||||
comma = "," if i < len(transforms) - 1 else ""
|
||||
lines.append(f"{indent} @('{pattern}', '{replacement}'){comma}")
|
||||
lines.append(f"{indent})")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _ps_stubs_array(stubs, indent=" "):
|
||||
"""Format stubs as a PowerShell @(@{...}, @{...}) array."""
|
||||
if not stubs:
|
||||
return "@()"
|
||||
lines = ["@("]
|
||||
for i, stub in enumerate(stubs):
|
||||
comma = "," if i < len(stubs) - 1 else ""
|
||||
body_escaped = stub["body"].replace("'", "''")
|
||||
lines.append(
|
||||
f"{indent} @{{ pkg='{stub['pkg']}'; name='{stub['name']}'; type='{stub['type']}';"
|
||||
)
|
||||
lines.append(f"{indent} body='{body_escaped}' }}{comma}")
|
||||
lines.append(f"{indent})")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def generate_script(
|
||||
sources,
|
||||
allow_paths,
|
||||
allow_recurse_paths,
|
||||
mc_root,
|
||||
swaps,
|
||||
transforms,
|
||||
stubs,
|
||||
forbidden_prefixes,
|
||||
cls_source,
|
||||
):
|
||||
now = datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
||||
separator = "# " + "=" * 60
|
||||
|
||||
sources_arr = _ps_array_literal(sources, " ")
|
||||
allow_paths_arr = _ps_array_literal(allow_paths, " ") if allow_paths else None
|
||||
allow_recurse_arr = (
|
||||
_ps_array_literal(allow_recurse_paths, " ") if allow_recurse_paths else None
|
||||
)
|
||||
forbidden_arr = _ps_array_literal(forbidden_prefixes, "")
|
||||
swaps_arr = _ps_swap_array(swaps, "")
|
||||
transforms_arr = _ps_transform_array(transforms, "")
|
||||
stubs_arr = _ps_stubs_array(stubs, "")
|
||||
|
||||
lines = []
|
||||
|
||||
lines.append(separator)
|
||||
lines.append(f"# Wave Migration Script")
|
||||
lines.append(f"# Generated by generate_wave_script.py on {now}")
|
||||
lines.append(f"# Source: {cls_source}")
|
||||
lines.append(separator)
|
||||
lines.append("")
|
||||
lines.append(". (Join-Path $PSScriptRoot 'Copy-Recursive.ps1')")
|
||||
lines.append(". (Join-Path $PSScriptRoot 'Nuke-Imports.ps1')")
|
||||
lines.append(". (Join-Path $PSScriptRoot 'Symbol-Swap.ps1')")
|
||||
lines.append(". (Join-Path $PSScriptRoot 'Static-Transform.ps1')")
|
||||
lines.append(". (Join-Path $PSScriptRoot 'Generate-Stubs.ps1')")
|
||||
lines.append(". (Join-Path $PSScriptRoot 'Dedupe-Imports.ps1')")
|
||||
lines.append(". (Join-Path $PSScriptRoot 'Build-Report.ps1')")
|
||||
lines.append("")
|
||||
|
||||
if sources:
|
||||
lines.append(separator)
|
||||
lines.append("# STEP 1: Copy files from mc1710 to core")
|
||||
lines.append(separator)
|
||||
copy_cmd = "Copy-Recursive `\n"
|
||||
copy_cmd += f" -Sources {sources_arr} `\n"
|
||||
if allow_paths_arr:
|
||||
copy_cmd += f" -AllowPaths {allow_paths_arr} `\n"
|
||||
if allow_recurse_arr:
|
||||
copy_cmd += f" -AllowRecursePaths {allow_recurse_arr} `\n"
|
||||
copy_cmd += f" -McRoot '{mc_root}'"
|
||||
lines.append(copy_cmd)
|
||||
lines.append("")
|
||||
|
||||
lines.append(separator)
|
||||
lines.append("# STEP 2: Nuke MC imports")
|
||||
lines.append(separator)
|
||||
lines.append(
|
||||
f"Nuke-Imports -Directory 'core/src/main/java' -ForbiddenPrefixes {forbidden_arr}"
|
||||
)
|
||||
lines.append("")
|
||||
|
||||
lines.append(separator)
|
||||
lines.append("# STEP 3: Symbol-Swap — replace MC types with PA abstractions")
|
||||
lines.append(separator)
|
||||
lines.append(f"Symbol-Swap -Directory 'core/src/main/java' -Swaps {swaps_arr}")
|
||||
lines.append("")
|
||||
|
||||
if transforms:
|
||||
lines.append(separator)
|
||||
lines.append("# STEP 4: Static-Transform — regex rewrites")
|
||||
lines.append(separator)
|
||||
lines.append(
|
||||
f"Static-Transform -Directory 'core/src/main/java' -Transforms {transforms_arr}"
|
||||
)
|
||||
lines.append("")
|
||||
|
||||
lines.append(separator)
|
||||
lines.append("# STEP 5: Generate stubs for missing dependencies")
|
||||
lines.append(separator)
|
||||
lines.append(f"Generate-Stubs -Stubs {stubs_arr}")
|
||||
lines.append("")
|
||||
|
||||
lines.append(separator)
|
||||
lines.append("# STEP 6: Deduplicate imports")
|
||||
lines.append(separator)
|
||||
lines.append("Dedupe-Imports -Directory 'core/src/main/java'")
|
||||
lines.append("")
|
||||
|
||||
lines.append(separator)
|
||||
lines.append("# STEP 7: Build and report")
|
||||
lines.append(separator)
|
||||
lines.append("Build-Report -GradleTask ':core:compileJava'")
|
||||
lines.append("")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate a complete .ps1 migration wave script"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--mappings",
|
||||
help="Path to mc_type_mappings.json or classifications JSON",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--sources",
|
||||
nargs="*",
|
||||
default=[],
|
||||
help="Java source files to migrate (relative to mc-root)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--sources-file",
|
||||
help="Text file with one source path per line",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--allow-paths",
|
||||
help="Comma-separated AllowPaths prefixes",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--allow-recurse-paths",
|
||||
help="Comma-separated AllowRecursePaths prefixes",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--mc-root",
|
||||
default="src/main/java",
|
||||
help="MC source root (default: src/main/java)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output",
|
||||
default="tools/migration/_wave.ps1",
|
||||
help="Output .ps1 file path (relative to project root)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--project-root",
|
||||
help="Project root (auto-detected if omitted)",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
project_root = (
|
||||
Path(args.project_root) if args.project_root else auto_detect_project_root()
|
||||
)
|
||||
|
||||
types, cls_source = _load_classifications_data(project_root, args.mappings)
|
||||
|
||||
sources = list(args.sources)
|
||||
if args.sources_file:
|
||||
sf = Path(args.sources_file)
|
||||
if not sf.is_absolute():
|
||||
sf = project_root / args.sources_file
|
||||
if sf.exists():
|
||||
with open(sf, "r", encoding="utf-8") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line and not line.startswith("#"):
|
||||
sources.append(line)
|
||||
|
||||
allow_paths = None
|
||||
if args.allow_paths:
|
||||
allow_paths = [p.strip() for p in args.allow_paths.split(",") if p.strip()]
|
||||
|
||||
allow_recurse_paths = None
|
||||
if args.allow_recurse_paths:
|
||||
allow_recurse_paths = [
|
||||
p.strip() for p in args.allow_recurse_paths.split(",") if p.strip()
|
||||
]
|
||||
|
||||
swaps = _build_all_swaps(types)
|
||||
transforms = _build_all_transforms(types)
|
||||
|
||||
script_content = generate_script(
|
||||
sources=sources,
|
||||
allow_paths=allow_paths,
|
||||
allow_recurse_paths=allow_recurse_paths,
|
||||
mc_root=args.mc_root,
|
||||
swaps=swaps,
|
||||
transforms=transforms,
|
||||
stubs=DEFAULT_STUBS,
|
||||
forbidden_prefixes=STANDARD_FORBIDDEN_PREFIXES,
|
||||
cls_source=cls_source,
|
||||
)
|
||||
|
||||
output_path = project_root / args.output
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(output_path, "w", encoding="utf-8", newline="\n") as f:
|
||||
f.write(script_content)
|
||||
|
||||
print()
|
||||
print(f"Wave Script Generator")
|
||||
print(f" Source: {cls_source}")
|
||||
print(f" Sources: {len(sources)} files")
|
||||
print(f" Swaps: {len(swaps)} entries")
|
||||
print(f" Transforms: {len(transforms)} patterns")
|
||||
print(f" Stubs: {len(DEFAULT_STUBS)} entries")
|
||||
print(f" Written to: {args.output}")
|
||||
print()
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user