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.
This commit is contained in:
Christian committed 2013-10-30 22:20:28 -04:00
1 parent 9efeaa8caa
commit ee8630bc55
7 files changed
+244 -16

No files matched your search

+14
View File
@@ -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();
}
+3 -1
View File
@@ -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");
@@ -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<String,APIContainer> apiContainers;
public void registerDataTable(ASMDataTable dataTable)
private static class APIContainer extends DummyModContainer {
private List<ArtifactVersion> referredMods;
private ArtifactVersion ownerMod;
private ArtifactVersion ourVersion;
private String providedAPI;
private File source;
private String version;
private Set<String> currentReferents;
private Set<String> 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<ArtifactVersion> getDependants()
{
return referredMods;
}
@Override
public List<ArtifactVersion> 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<String> candidateIds)
{
for (String modId : candidateIds)
{
addAPIReference(modId);
}
}
}
public void registerDataTableAndParseAPI(ASMDataTable dataTable)
{
this.dataTable = dataTable;
Set<ASMData> apiList = dataTable.getAll("cpw.mods.fml.common.API");
apiContainers = Maps.newHashMap();
for (ASMData data : apiList)
{
Map<String, Object> 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<ModCandidate> candidates = dataTable.getCandidatesFor(pkg);
for (ModCandidate candidate : candidates)
{
List<String> 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<ModContainer> mods, Map<String, ModContainer> nameLookup)
{
mods.addAll(apiContainers.values());
nameLookup.putAll(apiContainers);
}
public void cleanupAPIContainers(List<ModContainer> mods)
{
mods.removeAll(apiContainers.values());
}
public boolean hasAPI(String modId)
{
return apiContainers.containsKey(modId);
}
}
@@ -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<String, ASMData> 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;
@@ -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<ModContainer, SetMultimap<String,ASMData>> containerAnnotationData;
private List<ModContainer> containers = Lists.newArrayList();
private SetMultimap<String,ModCandidate> packageMap = HashMultimap.create();
public SetMultimap<String,ASMData> 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<ModCandidate> getCandidatesFor(String pkg)
{
return this.packageMap.get(pkg);
}
}
@@ -34,6 +34,9 @@ public class ModCandidate
private boolean isMinecraft;
private List<ASMModParser> baseModCandidateTypes = Lists.newArrayListWithCapacity(1);
private Set<String> foundClasses = Sets.newHashSet();
private List<ModContainer> mods;
private List<String> packages = Lists.newArrayList();
private ASMDataTable table;
public ModCandidate(File classPathRoot, File modContainer, ContainerType sourceType)
{
@@ -64,22 +67,28 @@ public class ModCandidate
}
public List<ModContainer> explore(ASMDataTable table)
{
List<ModContainer> 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<ModContainer> getContainedMods()
{
return mods;
}
public List<String> getContainedPackages()
{
return packages;
}
}
@@ -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<ModContainer> modList, Map<String, ModContainer> nameLookup)
{
buildGraph(modList, nameLookup);
HashMap<String, ModContainer> sortingNameLookup = Maps.newHashMap(nameLookup);
ModAPIManager.INSTANCE.injectAPIModContainers(modList, sortingNameLookup);
buildGraph(modList, sortingNameLookup);
}
private void buildGraph(List<ModContainer> modList, Map<String, ModContainer> 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);
}
}