package io.takari.maven.plugins.plugin; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.apache.maven.artifact.Artifact; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.codehaus.plexus.util.xml.pull.XmlPullParserException; import io.takari.incrementalbuild.Output; import io.takari.incrementalbuild.aggregator.AggregatorBuildContext; import io.takari.incrementalbuild.aggregator.InputAggregator; import io.takari.incrementalbuild.aggregator.InputSet; import io.takari.maven.plugins.TakariLifecycleMojo; import io.takari.maven.plugins.plugin.model.MojoDescriptor; import io.takari.maven.plugins.plugin.model.MojoParameter; import io.takari.maven.plugins.plugin.model.MojoRequirement; import io.takari.maven.plugins.plugin.model.PluginDescriptor; import io.takari.maven.plugins.plugin.model.io.xpp3.PluginDescriptorXpp3Reader; import io.takari.maven.plugins.plugin.model.io.xpp3.PluginDescriptorXpp3Writer; @Mojo(name = "plugin-descriptor", defaultPhase = LifecyclePhase.PROCESS_CLASSES, threadSafe = true, requiresDependencyResolution = ResolutionScope.COMPILE) public class PluginDescriptorMojo extends TakariLifecycleMojo { private static final String PATH_MOJOS_XML = "META-INF/takari/mojos.xml"; private static final String PATH_PLUGIN_XML = "META-INF/maven/plugin.xml"; @Parameter(defaultValue = "${project.groupId}", readonly = true) private String groupId; @Parameter(defaultValue = "${project.artifactId}", readonly = true) private String artifactId; @Parameter(defaultValue = "${project.version}", readonly = true) private String version; @Parameter(defaultValue = "${project.name}", readonly = true) private String name; @Parameter(defaultValue = "${project.description}", readonly = true) private String description; /** * The goal prefix that will appear before the ":". */ @Parameter private String goalPrefix; @Parameter(defaultValue = "${project.build.outputDirectory}", readonly = true) private File outputDirectory; @Parameter(defaultValue = "${project.compileArtifacts}", readonly = true) private List<Artifact> dependencies; // TODO must magically compile mojo configuration @Parameter(defaultValue = "${project.build.directory}/generated-sources/annotations") private File generatedSourcesDirectory; @Component private AggregatorBuildContext context; @Override protected void executeMojo() throws MojoExecutionException { try { InputSet inputSet = context.newInputSet(); final Set<File> classpathJars = new LinkedHashSet<>(); final Set<File> classpathFiles = new LinkedHashSet<>(); for (Artifact artifact : dependencies) { if (artifact.getFile().isFile()) { classpathJars.add(inputSet.addInput(artifact.getFile())); } else if (artifact.getFile().isDirectory()) { File file = new File(artifact.getFile(), PATH_MOJOS_XML); if (file.canRead()) { classpathFiles.add(inputSet.addInput(file)); } } } final Iterable<File> files = inputSet.addInputs(generatedSourcesDirectory, Collections.singleton("*.mojo.xml"), null); inputSet.aggregateIfNecessary(new File(outputDirectory, PATH_PLUGIN_XML), new InputAggregator() { @Override public void aggregate(Output<File> output, Iterable<File> inputs) throws IOException { createPluginXml(output, files, classpathFiles, classpathJars); } }); inputSet.aggregateIfNecessary(new File(outputDirectory, PATH_MOJOS_XML), new InputAggregator() { @Override public void aggregate(Output<File> output, Iterable<File> inputs) throws IOException { createMojosXml(output, files); } }); } catch (IOException e) { throw new MojoExecutionException("Could not create plugin descriptor", e); } } protected void createMojosXml(Output<File> output, Iterable<File> files) throws IOException { PluginDescriptor mojos = new PluginDescriptor(); List<MojoDescriptor> descriptors = new ArrayList<>(loadMojos(files).values()); Sorting.sortDescriptors(descriptors); for (MojoDescriptor descriptor : descriptors) { mojos.addMojo(descriptor); } try (OutputStream out = output.newOutputStream()) { new PluginDescriptorXpp3Writer().write(out, mojos); } } protected void createPluginXml(Output<File> output, Iterable<File> inputs, Set<File> classpathFiles, Set<File> classpathJars) throws IOException { Map<String, MojoDescriptor> classpathMojos = loadClasspathMojos(classpathFiles, classpathJars); Map<String, MojoDescriptor> mojos = loadMojos(inputs); PluginDescriptor plugin = newPluginDescriptor(); for (MojoDescriptor gleaned : mojos.values()) { MojoDescriptor descriptor = gleaned.clone(); if (descriptor.getGoal() == null) { continue; // abstract mojo, skip } for (String parent : descriptor.getSuperclasses()) { MojoDescriptor inherited = mojos.get(parent); if (inherited == null) { inherited = classpathMojos.get(parent); } if (inherited != null) { for (MojoParameter parameter : inherited.getParameters()) { if (!containsField(descriptor, parameter.getName())) { descriptor.addParameter(parameter.clone()); } } for (MojoRequirement requirement : inherited.getRequirements()) { if (!containsField(descriptor, requirement.getFieldName())) { descriptor.addRequirement(requirement.clone()); } } } } plugin.addMojo(descriptor); } try (OutputStream out = output.newOutputStream()) { new PluginDescriptorWriter().writeDescriptor(out, plugin); } } private boolean containsField(MojoDescriptor descriptor, String fieldName) { for (MojoParameter parameter : descriptor.getParameters()) { if (fieldName.equals(parameter.getName())) { return true; } } for (MojoRequirement requirement : descriptor.getRequirements()) { if (fieldName.equals(requirement.getFieldName())) { return true; } } return false; } private Map<String, MojoDescriptor> loadMojos(Iterable<File> inputs) throws IOException { Map<String, MojoDescriptor> mojos = new HashMap<>(); for (File mojoXml : inputs) { try (InputStream is = new FileInputStream(mojoXml)) { readMojosXml(mojos, is); } catch (XmlPullParserException e) { throw new IOException(e); } } return mojos; } private Map<String, MojoDescriptor> loadClasspathMojos(Set<File> files, Set<File> jars) { Map<String, MojoDescriptor> mojos = new HashMap<>(); for (File jarFile : jars) { try (ZipFile zip = new ZipFile(jarFile)) { ZipEntry entry = zip.getEntry(PATH_MOJOS_XML); if (entry != null) { try (InputStream is = zip.getInputStream(entry)) { readMojosXml(mojos, is); } continue; } entry = zip.getEntry(PATH_PLUGIN_XML); if (entry != null) { try (InputStream is = zip.getInputStream(entry)) { readPluginXml(mojos, is); } continue; } } catch (XmlPullParserException | IOException e) { logger.warn("Could not read dependency mojos.xml " + jarFile, e); } } for (File mojosXmlFile : files) { try (InputStream is = new FileInputStream(mojosXmlFile)) { readMojosXml(mojos, is); } catch (XmlPullParserException | IOException e) { logger.warn("Could not read dependency mojos.xml " + mojosXmlFile, e); } } return mojos; } private void readMojosXml(Map<String, MojoDescriptor> mojos, InputStream is) throws XmlPullParserException, IOException { PluginDescriptor pluginDescriptor = new PluginDescriptorXpp3Reader().read(is); for (MojoDescriptor mojo : pluginDescriptor.getMojos()) { mojos.put(mojo.getImplementation(), mojo); } } private void readPluginXml(Map<String, MojoDescriptor> mojos, InputStream is) throws XmlPullParserException, IOException { for (MojoDescriptor mojo : LegacyPluginDescriptors.readMojos(is)) { mojos.put(mojo.getImplementation(), mojo); } } private PluginDescriptor newPluginDescriptor() { String defaultGoalPrefix = org.apache.maven.plugin.descriptor.PluginDescriptor.getGoalPrefixFromArtifactId(artifactId); if (goalPrefix == null) { goalPrefix = defaultGoalPrefix; } else if (!goalPrefix.equals(defaultGoalPrefix)) { getLog().warn("\n\nGoal prefix is specified as: '" + goalPrefix + "'. " + "Maven currently expects it to be '" + defaultGoalPrefix + "'.\n"); } PluginDescriptor pluginDescriptor = new PluginDescriptor(); pluginDescriptor.setGroupId(groupId); pluginDescriptor.setArtifactId(artifactId); pluginDescriptor.setVersion(version); pluginDescriptor.setGoalPrefix(goalPrefix); pluginDescriptor.setName(name); pluginDescriptor.setDescription(description); pluginDescriptor.setInheritedByDefault(true); // see org.apache.maven.plugin.descriptor.PluginDescriptor.inheritedByDefault return pluginDescriptor; } }