From ee8630bc559217f495ec27793b85dca98cbcb898 Mon Sep 17 00:00:00 2001 From: Christian Date: Wed, 30 Oct 2013 22:20:28 -0400 Subject: [PATCH] Add in an API marker for API type packages. This does several things: 1. Packages marked as API will generate a new "modid" (the provides) that can be depended on. 2. Packages marked as API will be searched systemwide, and anything declaring that package (even without the API marker) will get an implicit dependency on the API package. 3. The API package itself will get a soft dependency on the "owner" package. @Optional elements can refer to the API packages instead of a concrete mod as well. --- fml/common/cpw/mods/fml/common/API.java | 14 ++ fml/common/cpw/mods/fml/common/Loader.java | 4 +- .../cpw/mods/fml/common/ModAPIManager.java | 183 +++++++++++++++++- .../asm/transformers/ModAPITransformer.java | 5 +- .../fml/common/discovery/ASMDataTable.java | 12 ++ .../fml/common/discovery/ModCandidate.java | 33 +++- .../mods/fml/common/toposort/ModSorter.java | 9 +- 7 files changed, 244 insertions(+), 16 deletions(-) create mode 100644 fml/common/cpw/mods/fml/common/API.java diff --git a/fml/common/cpw/mods/fml/common/API.java b/fml/common/cpw/mods/fml/common/API.java new file mode 100644 index 0000000000..b8a2b4b28a --- /dev/null +++ b/fml/common/cpw/mods/fml/common/API.java @@ -0,0 +1,14 @@ +package cpw.mods.fml.common; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PACKAGE) +public @interface API { + String owner(); + String provides(); + String apiVersion(); +} diff --git a/fml/common/cpw/mods/fml/common/Loader.java b/fml/common/cpw/mods/fml/common/Loader.java index 17fad9262a..55d1b86a77 100644 --- a/fml/common/cpw/mods/fml/common/Loader.java +++ b/fml/common/cpw/mods/fml/common/Loader.java @@ -482,12 +482,15 @@ public class Loader modController = new LoadController(this); modController.transition(LoaderState.LOADING, false); ModDiscoverer disc = identifyMods(); + ModAPIManager.INSTANCE.manageAPI(modClassLoader, disc); disableRequestedMods(); FMLLog.fine("Reloading logging properties from %s", loggingProperties.getPath()); FMLRelaunchLog.loadLogConfiguration(loggingProperties); FMLLog.fine("Reloaded logging properties"); modController.distributeStateMessage(FMLLoadEvent.class); sortModList(); + ModAPIManager.INSTANCE.cleanupAPIContainers(modController.getActiveModList()); + ModAPIManager.INSTANCE.cleanupAPIContainers(mods); mods = ImmutableList.copyOf(mods); for (File nonMod : disc.getNonModLibs()) { @@ -504,7 +507,6 @@ public class Loader } } } - ModAPIManager.INSTANCE.buildAPITransformer(modClassLoader, disc); modController.transition(LoaderState.CONSTRUCTING, false); modController.distributeStateMessage(LoaderState.CONSTRUCTING, modClassLoader, disc.getASMTable()); FMLLog.fine("Mod signature data"); diff --git a/fml/common/cpw/mods/fml/common/ModAPIManager.java b/fml/common/cpw/mods/fml/common/ModAPIManager.java index f8c04ddf07..eb714c941c 100644 --- a/fml/common/cpw/mods/fml/common/ModAPIManager.java +++ b/fml/common/cpw/mods/fml/common/ModAPIManager.java @@ -1,22 +1,199 @@ package cpw.mods.fml.common; +import java.io.File; +import java.security.acl.Owner; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + import cpw.mods.fml.common.asm.transformers.ModAPITransformer; import cpw.mods.fml.common.discovery.ASMDataTable; +import cpw.mods.fml.common.discovery.ASMDataTable.ASMData; +import cpw.mods.fml.common.discovery.ModCandidate; import cpw.mods.fml.common.discovery.ModDiscoverer; +import cpw.mods.fml.common.functions.ModIdFunction; +import cpw.mods.fml.common.versioning.ArtifactVersion; +import cpw.mods.fml.common.versioning.DefaultArtifactVersion; +import cpw.mods.fml.common.versioning.VersionParser; public class ModAPIManager { public static final ModAPIManager INSTANCE = new ModAPIManager(); private ModAPITransformer transformer; private ASMDataTable dataTable; + private Map apiContainers; - public void registerDataTable(ASMDataTable dataTable) + private static class APIContainer extends DummyModContainer { + private List referredMods; + private ArtifactVersion ownerMod; + private ArtifactVersion ourVersion; + private String providedAPI; + private File source; + private String version; + private Set currentReferents; + private Set packages; + + public APIContainer(String providedAPI, String apiVersion, File source, ArtifactVersion ownerMod) + { + this.providedAPI = providedAPI; + this.version = apiVersion; + this.ownerMod = ownerMod; + this.ourVersion = new DefaultArtifactVersion(providedAPI, apiVersion); + this.referredMods = Lists.newArrayList(); + this.source = source; + this.currentReferents = Sets.newHashSet(); + this.packages = Sets.newHashSet(); + } + + @Override + public File getSource() + { + return source; + } + @Override + public String getVersion() + { + return version; + } + @Override + public String getName() + { + return "API: "+providedAPI; + } + @Override + public String getModId() + { + return "API:"+providedAPI; + } + @Override + public List getDependants() + { + return referredMods; + } + + @Override + public List getDependencies() + { + return ImmutableList.of(ownerMod); + } + + @Override + public ArtifactVersion getProcessedVersion() + { + return ourVersion; + } + + public void validate(String providedAPI, String apiOwner, String apiVersion) + { + // TODO Compare this annotation data to the one we first found. Maybe barf if there is inconsistency? + } + + @Override + public String toString() + { + return "APIContainer{"+providedAPI+":"+version+"}"; + } + + public void addAPIReference(String embedded) + { + currentReferents.add(embedded); + referredMods.add(VersionParser.parseVersionReference(embedded)); + } + + public void addOwnedPackage(String apiPackage) + { + packages.add(apiPackage); + } + + public void addAPIReferences(List candidateIds) + { + for (String modId : candidateIds) + { + addAPIReference(modId); + } + } + } + public void registerDataTableAndParseAPI(ASMDataTable dataTable) { this.dataTable = dataTable; + + Set apiList = dataTable.getAll("cpw.mods.fml.common.API"); + + apiContainers = Maps.newHashMap(); + + for (ASMData data : apiList) + { + Map annotationInfo = data.getAnnotationInfo(); + String apiPackage = data.getClassName().substring(0,data.getClassName().indexOf(".package-info")); + String providedAPI = (String) annotationInfo.get("provides"); + String apiOwner = (String) annotationInfo.get("owner"); + String apiVersion = (String) annotationInfo.get("apiVersion"); + APIContainer container = apiContainers.get(providedAPI); + if (container == null) + { + container = new APIContainer(providedAPI, apiVersion, data.getCandidate().getModContainer(), VersionParser.parseVersionReference(apiOwner)); + apiContainers.put(providedAPI, container); + } + else + { + container.validate(providedAPI, apiOwner, apiVersion); + } + container.addOwnedPackage(apiPackage); + for (ModContainer mc : data.getCandidate().getContainedMods()) + { + String embeddedIn = mc.getModId(); + FMLLog.fine("Found API %s (owned by %s providing %s) embedded in %s",apiPackage, apiOwner, providedAPI, embeddedIn); + if (!embeddedIn.equals(apiOwner)) + { + container.addAPIReference(embeddedIn); + } + } + } + + for (APIContainer container : apiContainers.values()) + { + for (String pkg : container.packages) + { + Set candidates = dataTable.getCandidatesFor(pkg); + for (ModCandidate candidate : candidates) + { + List candidateIds = Lists.transform(candidate.getContainedMods(), new ModIdFunction()); + if (!candidateIds.contains(container.ownerMod.getLabel()) && !container.currentReferents.containsAll(candidateIds)) + { + FMLLog.info("Found mod(s) %s containing declared API package %s (owned by %s) without associated API reference",candidateIds, pkg, container.ownerMod); + container.addAPIReferences(candidateIds); + } + } + } + FMLLog.fine("Creating API container dummy for API %s: owner: %s, dependents: %s", container.providedAPI, container.ownerMod, container.referredMods); + } } - public void buildAPITransformer(ModClassLoader modClassLoader, ModDiscoverer discoverer) + public void manageAPI(ModClassLoader modClassLoader, ModDiscoverer discoverer) { - registerDataTable(discoverer.getASMTable()); + registerDataTableAndParseAPI(discoverer.getASMTable()); transformer = modClassLoader.addModAPITransformer(dataTable); } + + public void injectAPIModContainers(List mods, Map nameLookup) + { + mods.addAll(apiContainers.values()); + nameLookup.putAll(apiContainers); + } + + public void cleanupAPIContainers(List mods) + { + mods.removeAll(apiContainers.values()); + } + + public boolean hasAPI(String modId) + { + return apiContainers.containsKey(modId); + } } diff --git a/fml/common/cpw/mods/fml/common/asm/transformers/ModAPITransformer.java b/fml/common/cpw/mods/fml/common/asm/transformers/ModAPITransformer.java index 62f5730c15..5a0330d8a7 100644 --- a/fml/common/cpw/mods/fml/common/asm/transformers/ModAPITransformer.java +++ b/fml/common/cpw/mods/fml/common/asm/transformers/ModAPITransformer.java @@ -20,6 +20,7 @@ import com.google.common.collect.Sets; import cpw.mods.fml.common.FMLLog; import cpw.mods.fml.common.Loader; +import cpw.mods.fml.common.ModAPIManager; import cpw.mods.fml.common.Optional; import cpw.mods.fml.common.discovery.ASMDataTable; import cpw.mods.fml.common.discovery.ASMDataTable.ASMData; @@ -29,7 +30,7 @@ import net.minecraft.launchwrapper.IClassTransformer; public class ModAPITransformer implements IClassTransformer { - private static final boolean logDebugInfo = Boolean.valueOf(System.getProperty("fml.debugAPITransformer", "false")); + private static final boolean logDebugInfo = Boolean.valueOf(System.getProperty("fml.debugAPITransformer", "true")); private ListMultimap optionals; @Override @@ -48,7 +49,7 @@ public class ModAPITransformer implements IClassTransformer { { String modId = (String) optional.getAnnotationInfo().get("modid"); - if (Loader.isModLoaded(modId)) + if (Loader.isModLoaded(modId) || ModAPIManager.INSTANCE.hasAPI(modId)) { if (logDebugInfo) FMLRelaunchLog.finest("Optional removal skipped - mod present %s", modId); continue; diff --git a/fml/common/cpw/mods/fml/common/discovery/ASMDataTable.java b/fml/common/cpw/mods/fml/common/discovery/ASMDataTable.java index 308e6a76fb..1ec55964ea 100644 --- a/fml/common/cpw/mods/fml/common/discovery/ASMDataTable.java +++ b/fml/common/cpw/mods/fml/common/discovery/ASMDataTable.java @@ -17,6 +17,7 @@ import java.util.Map; import java.util.Set; import com.google.common.base.Predicate; +import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSetMultimap; @@ -96,6 +97,7 @@ public class ASMDataTable private Map> containerAnnotationData; private List containers = Lists.newArrayList(); + private SetMultimap packageMap = HashMultimap.create(); public SetMultimap getAnnotationsFor(ModContainer container) { @@ -126,4 +128,14 @@ public class ASMDataTable { this.containers.add(container); } + + public void registerPackage(ModCandidate modCandidate, String pkg) + { + this.packageMap.put(pkg,modCandidate); + } + + public Set getCandidatesFor(String pkg) + { + return this.packageMap.get(pkg); + } } diff --git a/fml/common/cpw/mods/fml/common/discovery/ModCandidate.java b/fml/common/cpw/mods/fml/common/discovery/ModCandidate.java index 172526116f..7581f67ca5 100644 --- a/fml/common/cpw/mods/fml/common/discovery/ModCandidate.java +++ b/fml/common/cpw/mods/fml/common/discovery/ModCandidate.java @@ -34,6 +34,9 @@ public class ModCandidate private boolean isMinecraft; private List baseModCandidateTypes = Lists.newArrayListWithCapacity(1); private Set foundClasses = Sets.newHashSet(); + private List mods; + private List packages = Lists.newArrayList(); + private ASMDataTable table; public ModCandidate(File classPathRoot, File modContainer, ContainerType sourceType) { @@ -64,22 +67,28 @@ public class ModCandidate } public List explore(ASMDataTable table) { - List mods = sourceType.findMods(this, table); + this.table = table; + this.mods = sourceType.findMods(this, table); if (!baseModCandidateTypes.isEmpty()) { FMLLog.info("Attempting to reparse the mod container %s", getModContainer().getName()); - return sourceType.findMods(this, table); - } - else - { - return mods; + this.mods = sourceType.findMods(this, table); } + return this.mods; } public void addClassEntry(String name) { - String className = name.substring(0, name.lastIndexOf('.')).replace('.', '/'); // strip the .class and . -> / - foundClasses.add(className); + String className = name.substring(0, name.lastIndexOf('.')); // strip the .class + foundClasses.add(className.replace('.', '/')); + className = className.replace('/','.'); + int pkgIdx = className.lastIndexOf('.'); + if (pkgIdx > -1) + { + String pkg = className.substring(0,pkgIdx); + packages.add(pkg); + this.table.registerPackage(this,pkg); + } } public boolean isClasspath() @@ -106,4 +115,12 @@ public class ModCandidate { return foundClasses; } + public List getContainedMods() + { + return mods; + } + public List getContainedPackages() + { + return packages; + } } diff --git a/fml/common/cpw/mods/fml/common/toposort/ModSorter.java b/fml/common/cpw/mods/fml/common/toposort/ModSorter.java index bbdaa4e521..491f9800a0 100644 --- a/fml/common/cpw/mods/fml/common/toposort/ModSorter.java +++ b/fml/common/cpw/mods/fml/common/toposort/ModSorter.java @@ -13,13 +13,16 @@ package cpw.mods.fml.common.toposort; import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Map; import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import cpw.mods.fml.common.DummyModContainer; import cpw.mods.fml.common.Loader; +import cpw.mods.fml.common.ModAPIManager; import cpw.mods.fml.common.ModContainer; import cpw.mods.fml.common.toposort.TopologicalSort.DirectedGraph; import cpw.mods.fml.common.versioning.ArtifactVersion; @@ -39,7 +42,9 @@ public class ModSorter public ModSorter(List modList, Map nameLookup) { - buildGraph(modList, nameLookup); + HashMap sortingNameLookup = Maps.newHashMap(nameLookup); + ModAPIManager.INSTANCE.injectAPIModContainers(modList, sortingNameLookup); + buildGraph(modList, sortingNameLookup); } private void buildGraph(List modList, Map nameLookup) @@ -85,7 +90,7 @@ public class ModSorter else { modGraph.addEdge(before, mod); - if (Loader.isModLoaded(modid)) { + if (nameLookup.containsKey(modid) || Loader.isModLoaded(modid)) { modGraph.addEdge(nameLookup.get(modid), mod); } }