All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.bytebuddy.build.maven.ByteBuddyMojo Maven / Gradle / Ivy

/*
 * Copyright 2014 - Present Rafael Winterhalter
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.bytebuddy.build.maven;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.bytebuddy.ClassFileVersion;
import net.bytebuddy.build.BuildLogger;
import net.bytebuddy.build.EntryPoint;
import net.bytebuddy.build.Plugin;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.dynamic.scaffold.inline.MethodNameTransformer;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.utility.CompoundList;
import net.bytebuddy.utility.nullability.MaybeNull;
import net.bytebuddy.utility.nullability.UnknownNull;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.PluginManagement;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.*;
import org.apache.maven.project.MavenProject;
import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
import org.codehaus.plexus.util.Scanner;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.collection.CollectRequest;
import org.eclipse.aether.collection.DependencyCollectionException;
import org.eclipse.aether.graph.DependencyNode;
import org.eclipse.aether.resolution.DependencyRequest;
import org.eclipse.aether.resolution.DependencyResolutionException;
import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator;
import org.sonatype.plexus.build.incremental.BuildContext;

import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * A Maven plugin for applying Byte Buddy transformations during a build.
 */
public abstract class ByteBuddyMojo extends AbstractMojo {

    /**
     * The file extension for Java source files.
     */
    private static final String JAVA_FILE_EXTENSION = ".java";

    /**
     * The file extension for Java class files.
     */
    private static final String JAVA_CLASS_EXTENSION = ".class";

    /**
     * The Maven project.
     */
    @UnknownNull
    @Parameter(defaultValue = "${project}", readonly = true)
    public MavenProject project;

    /**
     * The current execution of this plugin.
     */
    @UnknownNull
    @Parameter(defaultValue = "${mojoExecution}", readonly = true)
    public MojoExecution execution;

    /**
     * The currently used repository system.
     */
    @UnknownNull
    @Component
    public RepositorySystem repositorySystem;

    /**
     * The currently used system session for the repository system.
     */
    @MaybeNull
    @Parameter(defaultValue = "${repositorySystemSession}", readonly = true)
    public RepositorySystemSession repositorySystemSession;

    /**
     * 

* The list of transformations. A transformation must specify the {@code plugin} property, containing the name of a class to apply. * Additionally, it is possible to optionally specify Maven coordinates for a project that contains this plugin class as {@code groupId}, * {@code artifactId} and {@code version}. If any of the latter properties is not set, this projects coordinate is used. *

*

* For example, the following configuration applies the {@code foo.Bar} class which must implement {@link Plugin} from artifact * {@code transform-artifact} with this project's group and version: *

*
{@code
     * 
     *   
     *     foo.Bar< /plugin>
     *     transform-artifact< /artifactId>
     *   < /transformation>
     * < /transformations>
     * }
*

* If the list of {@code transformations} is empty or is not supplied at all, this plugin does not apply but prints a warning. *

*/ @MaybeNull @Parameter public List transformations; /** *

* The initializer used for creating a {@link net.bytebuddy.ByteBuddy} instance and for applying a transformation. By default, * a type is rebased. The initializer's {@code entryPoint} property can be set to any constant name of {@link EntryPoint.Default} * or to a class name. If the latter applies, it is possible to set Maven coordinates for a Maven plugin which defines this * class where any property defaults to this project's coordinates. *

*

* For example, the following configuration applies the {@code foo.Qux} class which must implement {@link EntryPoint} from * artifact {@code initialization-artifact} with this project's group and version: *

*
{@code
     * 
     *   foo.Qux< /entryPoint>
     *   initialization-artifact< /artifactId>
     * < /initialization>
     * }
*/ @MaybeNull @Parameter public Initialization initialization; /** * Specifies the method name suffix that is used when type's method need to be rebased. If this property is not * set or is empty, a random suffix will be appended to any rebased method. If this property is set, the supplied * value is appended to the original method name. */ @MaybeNull @Parameter public String suffix; /** * When transforming classes during build time, it is not possible to apply any transformations which require a class * in its loaded state. Such transformations might imply setting a type's static field to a user interceptor or similar * transformations. If this property is set to {@code false}, this plugin does not throw an exception if such a live * initializer is defined during a transformation process. */ @Parameter(defaultValue = "true", required = true) public boolean failOnLiveInitializer; /** * When set to {@code true}, this mojo is not applied to the current module. */ @Parameter(defaultValue = "false", required = true) public boolean skip; /** * When set to {@code true}, this mojo warns of an non-existent output directory. */ @Parameter(defaultValue = "true", required = true) public boolean warnOnMissingOutputDirectory; /** * When set to {@code true}, this mojo warns of not having transformed any types. */ @Parameter(defaultValue = "true", required = true) public boolean warnOnEmptyTypeSet; /** * When set to {@code true}, this mojo fails immediately if a plugin cannot be applied. */ @Parameter(defaultValue = "true", required = true) public boolean failFast; /** * When set to {@code true}, the debug information of class files should be parsed to extract parameter names. */ @Parameter(defaultValue = "false", required = true) public boolean extendedParsing; /** * Determines if the build should discover Byte Buddy build plugins on this Maven plugin's class loader. * Discovered plugins are stored by their name in the /META-INF/net.bytebuddy/build.plugins file * where each line contains the fully qualified class name. Discovered plugins are not provided with any * explicit constructor arguments. */ @MaybeNull @Parameter(defaultValue = "EMPTY", required = true) public Discovery discovery; /** * Scans the class path (or test class path) for Byte Buddy plugins to apply. This is not normally recommended as * it might cause a spurious application of plugins that are accidentally configured on the class path. It can * however serve as a convenience in projects with few dependencies where this allows for the use of Maven's * dependency version management. */ @Parameter(defaultValue = "false", required = true) public boolean classPathDiscovery; /** * Indicates the amount of threads used for parallel type processing or {@code 0} for serial processing. */ @Parameter(defaultValue = "0", required = true) public int threads; /** * Determines the tolerance of many milliseconds between this plugin run and the last edit are permitted * for considering a file as stale if the plugin was executed before. Can be set to {@code -1} to disable. */ @Parameter(defaultValue = "0", required = true) public int staleMilliseconds; /** * {@inheritDoc} */ public void execute() throws MojoExecutionException, MojoFailureException { if (project == null || repositorySystem == null || discovery == null) { throw new MojoExecutionException("Plugin is not initialized correctly"); } else if (skip) { getLog().info("Not applying instrumentation as a result of plugin configuration."); return; } List transformers = new ArrayList(); Set undiscoverable = new HashSet(); if (transformations != null) { for (Transformation transformation : transformations) { transformers.add(new Transformer.ForConfiguredPlugin(transformation)); if (discovery.isRecordConfiguration()) { undiscoverable.add(transformation.getPlugin()); } } } Map coordinates = new HashMap(); if (project.getDependencyManagement() != null) { for (Dependency dependency : project.getDependencyManagement().getDependencies()) { coordinates.put(new Coordinate(dependency.getGroupId(), dependency.getArtifactId()), dependency.getVersion()); } } List elements = resolveClassPathElements(coordinates); if (discovery.isDiscover(transformers)) { try { Enumeration plugins = ByteBuddyMojo.class.getClassLoader().getResources(Plugin.Engine.Default.PLUGIN_FILE); while (plugins.hasMoreElements()) { discover(plugins.nextElement().openStream(), undiscoverable, transformers, null); } if (classPathDiscovery) { for (String element : elements) { File artifact = new File(element); if (artifact.isFile()) { JarFile file = new JarFile(artifact); try { JarEntry entry = file.getJarEntry(Plugin.Engine.Default.PLUGIN_FILE); if (entry != null) { discover(file.getInputStream(entry), undiscoverable, transformers, elements); } } finally { file.close(); } } else { File file = new File(artifact, Plugin.Engine.Default.PLUGIN_FILE); if (file.exists()) { discover(new FileInputStream(file), undiscoverable, transformers, elements); } } } } } catch (IOException exception) { throw new MojoExecutionException("Failed plugin discovery", exception); } } if (transformers.isEmpty()) { getLog().warn("No transformations are specified or discovered. Skipping plugin application."); return; } else { getLog().debug(transformers.size() + " plugins are being applied via configuration and discovery"); } try { apply(transformers, elements, coordinates); } catch (IOException exception) { throw new MojoFailureException("Error during writing process", exception); } } /** * Resolves the class path elements of the relevant output directory. * * @param coordinates Versions for managed dependencies. * @return The class path elements of the relevant output directory. * @throws MojoExecutionException If the user configuration results in an error. * @throws MojoFailureException If the plugin application raises an error. */ protected abstract List resolveClassPathElements(Map coordinates) throws MojoExecutionException, MojoFailureException; /** * Applies this mojo for the given setup. * * @param transformers The transformers to apply. * @param elements The class path elements to consider. * @param coordinates Versions for managed dependencies. * @throws MojoExecutionException If the plugin fails due to a user error. * @throws MojoFailureException If the plugin fails due to an application error. * @throws IOException If an I/O exception occurs. */ protected abstract void apply(List transformers, List elements, Map coordinates) throws MojoExecutionException, MojoFailureException, IOException; /** * Applies the instrumentation. * * @param classPath An iterable over all class path elements. * @param coordinates Versions for managed dependencies. * @param transformers The transformers to apply. * @param source The source for the plugin engine's application. * @param target The target for the plugin engine's application. * @param file The file representing the source location. * @param filtered {@code true} if files are already filtered and should not be checked for staleness. * @return A summary of the applied transformation. * @throws MojoExecutionException If the plugin cannot be applied. * @throws IOException If an I/O exception occurs. */ @SuppressWarnings("unchecked") protected Plugin.Engine.Summary transform(List classPath, Map coordinates, List transformers, Plugin.Engine.Source source, Plugin.Engine.Target target, File file, boolean filtered) throws MojoExecutionException, IOException { File staleness = new File(project.getBuild().getDirectory(), "maven-status" + File.separator + execution.getArtifactId() + File.separator + execution.getGoal() + File.separator + execution.getExecutionId() + File.separator + "staleness"); StalenessFilter stalenessFilter; if (filtered || staleMilliseconds < 0) { stalenessFilter = null; getLog().debug("Stale file detection is disabled"); } else if (staleness.exists()) { stalenessFilter = new StalenessFilter(getLog(), staleness.lastModified() + staleMilliseconds); source = new Plugin.Engine.Source.Filtering(source, stalenessFilter); getLog().debug("Using stale file detection with a margin of " + staleMilliseconds + " milliseconds"); } else { stalenessFilter = null; getLog().debug("Did not discover previous staleness file"); } ClassLoaderResolver classLoaderResolver = new ClassLoaderResolver(getLog(), repositorySystem, repositorySystemSession == null ? MavenRepositorySystemUtils.newSession() : repositorySystemSession, project.getRemotePluginRepositories()); try { List factories = new ArrayList(transformers.size()); for (Transformer transformer : transformers) { String plugin = transformer.getPlugin(); try { factories.add(new Plugin.Factory.UsingReflection((Class) Class.forName(plugin, false, transformer.toClassLoader(classLoaderResolver, coordinates, project.getGroupId(), project.getArtifactId(), project.getVersion(), project.getPackaging()))) .with(transformer.toArgumentResolvers()) .with(Plugin.Factory.UsingReflection.ArgumentResolver.ForType.of(File.class, file), Plugin.Factory.UsingReflection.ArgumentResolver.ForType.of(Log.class, getLog()), Plugin.Factory.UsingReflection.ArgumentResolver.ForType.of(BuildLogger.class, new MavenBuildLogger(getLog())))); getLog().info("Resolved plugin: " + plugin); } catch (Throwable throwable) { throw new MojoExecutionException("Cannot resolve plugin: " + plugin, throwable); } } String managed = coordinates.get(new Coordinate(project.getGroupId(), project.getArtifactId())); EntryPoint entryPoint = (initialization == null ? new Initialization() : initialization).getEntryPoint(classLoaderResolver, project.getGroupId(), project.getArtifactId(), managed == null ? project.getVersion() : managed, project.getPackaging()); getLog().info("Resolved entry point: " + entryPoint); List classFileLocators = new ArrayList(classPath.size()); classFileLocators.add(ClassFileLocator.ForClassLoader.ofPlatformLoader()); for (String element : classPath) { File artifact = new File(element); classFileLocators.add(artifact.isFile() ? ClassFileLocator.ForJarFile.of(artifact) : new ClassFileLocator.ForFolder(artifact)); } ClassFileLocator classFileLocator = new ClassFileLocator.Compound(classFileLocators); Plugin.Engine.Summary summary; try { getLog().info("Processing class files located in in: " + file); Plugin.Engine pluginEngine; try { String javaVersionString = findJavaVersionString(project, "release"); if (javaVersionString == null) { javaVersionString = findJavaVersionString(project, "target"); } ClassFileVersion classFileVersion; if (javaVersionString == null) { classFileVersion = ClassFileVersion.ofThisVm(ClassFileVersion.JAVA_V5); getLog().warn("Could not locate Java target version, build is JDK dependant: " + classFileVersion.getMajorVersion()); } else { classFileVersion = ClassFileVersion.ofJavaVersionString(javaVersionString); getLog().debug("Java version detected: " + javaVersionString); } pluginEngine = Plugin.Engine.Default.of(entryPoint, classFileVersion, suffix == null || suffix.length() == 0 ? MethodNameTransformer.Suffixing.withRandomSuffix() : new MethodNameTransformer.Suffixing(suffix)); } catch (Throwable throwable) { throw new MojoExecutionException("Cannot create plugin engine", throwable); } try { summary = pluginEngine .with(extendedParsing ? Plugin.Engine.PoolStrategy.Default.EXTENDED : Plugin.Engine.PoolStrategy.Default.FAST) .with(classFileLocator) .with(new TransformationLogger(getLog())) .withErrorHandlers(Plugin.Engine.ErrorHandler.Enforcing.ALL_TYPES_RESOLVED, failOnLiveInitializer ? Plugin.Engine.ErrorHandler.Enforcing.NO_LIVE_INITIALIZERS : Plugin.Engine.Listener.NoOp.INSTANCE, failFast ? Plugin.Engine.ErrorHandler.Failing.FAIL_FAST : Plugin.Engine.ErrorHandler.Failing.FAIL_LAST) .with(threads == 0 ? Plugin.Engine.Dispatcher.ForSerialTransformation.Factory.INSTANCE : new Plugin.Engine.Dispatcher.ForParallelTransformation.WithThrowawayExecutorService.Factory(threads)) .apply(source, target, factories); } catch (Throwable throwable) { throw new MojoExecutionException("Failed to transform class files in " + file, throwable); } } finally { classFileLocator.close(); } if (!summary.getFailed().isEmpty()) { throw new MojoExecutionException(summary.getFailed() + " type transformation(s) have failed"); } else if (warnOnEmptyTypeSet && summary.getTransformed().isEmpty()) { if (stalenessFilter != null && stalenessFilter.getFiltered() > 0) { getLog().info("No types were transformed during plugin execution but " + stalenessFilter.getFiltered() + " class file(s) were considered stale"); } else { getLog().warn("No types were transformed during plugin execution"); } } else { getLog().info("Transformed " + summary.getTransformed().size() + " type(s)"); } if (!(staleness.getParentFile().isDirectory() || staleness.getParentFile().mkdirs()) || (!staleness.createNewFile() && (!staleness.delete() || !staleness.createNewFile()))) { throw new MojoExecutionException("Failed to define instrumentation staleness: " + staleness.getAbsolutePath()); } return summary; } finally { classLoaderResolver.close(); } } /** * Discovers plugins from an input stream representing a META-INF/net.bytebuddy/build.plugins file. * * @param inputStream The input stream to read from. * @param undiscoverable A set of undiscoverable plugins. * @param transformers The list of transformers to add discovered plugins to. * @param classPath The class path elements to add if a plugin is loaded from the class path or {@code null} if the plugin is discovered as a dependency * @throws IOException If an I/O exception occurs. */ private void discover(InputStream inputStream, Set undiscoverable, List transformers, @MaybeNull List classPath) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); try { String line; while ((line = reader.readLine()) != null) { if (undiscoverable.add(line)) { transformers.add(classPath == null ? new Transformer.ForDiscoveredPlugin(line) : new Transformer.ForDiscoveredPlugin.FromClassLoader(line, classPath)); getLog().debug("Registered discovered plugin: " + line); } else { getLog().info("Skipping discovered plugin " + line + " which was previously discovered or registered"); } } } finally { reader.close(); } } /** * Makes a best effort of locating the configured Java version. * * @param project The relevant Maven project. * @param property The targeted Maven property. * @return The Java version string of the configured build Java version or {@code null} if no explicit configuration was detected. */ @MaybeNull private static String findJavaVersionString(MavenProject project, String property) { do { String value = project.getProperties().getProperty("maven.compiler." + property); if (value != null) { return value; } PluginManagement management = project.getPluginManagement(); for (org.apache.maven.model.Plugin plugin : management == null ? project.getBuildPlugins() : CompoundList.of(project.getBuildPlugins(), management.getPlugins())) { if ("maven-compiler-plugin".equals(plugin.getArtifactId())) { if (plugin.getConfiguration() instanceof Xpp3Dom) { Xpp3Dom node = ((Xpp3Dom) plugin.getConfiguration()).getChild(property); if (node != null) { return node.getValue(); } } } } project = project.getParent(); } while (project != null); return null; } /** * Matches elements which represent a Java class that is represented in the list or an inner class of the classes represented in the list. */ private static class FilePrefixMatcher extends ElementMatcher.Junction.ForNonNullValues { /** * A list of names to match. */ private final List names; /** * Create a new matcher for a list of names. * * @param names A list of included names. */ private FilePrefixMatcher(List names) { this.names = names; } /** * {@inheritDoc} */ protected boolean doMatch(Plugin.Engine.Source.Element target) { for (String name : names) { if (target.getName().equals(name + JAVA_CLASS_EXTENSION) || target.getName().startsWith(name + "$") && target.getName().endsWith(JAVA_CLASS_EXTENSION)) { return true; } } return false; } } /** * A version of the plugin that is bound to Maven's lifecycle. */ public abstract static class ForLifecycleTypes extends ByteBuddyMojo { /** * The build context to support incremental builds. */ @MaybeNull @Component public BuildContext context; /** * Determines if plugins are attempted to be built incrementally. */ @Parameter(defaultValue = "false", required = true) public boolean incremental; /** * Returns the output directory to search for class files. * * @return The output directory to search for class files. */ protected abstract String getOutputDirectory(); /** * Returns the source directory that determines the class files to process. * * @return The source directory that serves as an input for the transformation. */ @MaybeNull protected abstract String getSourceDirectory(); @Override protected void apply(List transformers, List elements, Map coordinates) throws MojoExecutionException, IOException { File root = new File(getOutputDirectory()); if (!root.exists()) { if (warnOnMissingOutputDirectory) { getLog().warn("Skipping instrumentation due to missing directory: " + root); } else { getLog().info("Skipping instrumentation due to missing directory: " + root); } return; } else if (!root.isDirectory()) { throw new MojoExecutionException("Not a directory: " + root); } String sourceDirectory = getSourceDirectory(); if (incremental && context != null && sourceDirectory != null) { getLog().debug("Considering incremental build with context: " + context); Plugin.Engine.Source source; if (context.isIncremental()) { Scanner scanner = context.newScanner(new File(sourceDirectory)); scanner.scan(); List names = new ArrayList(); for (String file : scanner.getIncludedFiles()) { if (file.endsWith(JAVA_FILE_EXTENSION)) { names.add(file.substring(0, file.length() - JAVA_FILE_EXTENSION.length())); } } source = new Plugin.Engine.Source.Filtering(new Plugin.Engine.Source.ForFolder(root), new FilePrefixMatcher(names)); getLog().debug("Incrementally processing: " + names); } else { source = new Plugin.Engine.Source.ForFolder(root); getLog().debug("Cannot build incrementally - all class files are processed"); } Plugin.Engine.Summary summary = transform(elements, coordinates, transformers, source, new Plugin.Engine.Target.ForFolder(root), root, true); for (TypeDescription typeDescription : summary.getTransformed()) { context.refresh(new File(getOutputDirectory(), typeDescription.getName() + JAVA_CLASS_EXTENSION)); } } else { getLog().debug("Not applying incremental build with context: " + context); transform(elements, coordinates, transformers, new Plugin.Engine.Source.ForFolder(root), new Plugin.Engine.Target.ForFolder(root), root, false); } } /** * A Byte Buddy plugin that transforms a project's production class files. */ public abstract static class ForProductionTypes extends ForLifecycleTypes { @Override protected String getOutputDirectory() { return project.getBuild().getOutputDirectory(); } @MaybeNull @Override protected String getSourceDirectory() { return project.getBuild().getSourceDirectory(); } /** * A Byte Buddy plugin that transforms a project's production class files where runtime class * path elements are not included. */ @Mojo(name = "transform", defaultPhase = LifecyclePhase.PROCESS_CLASSES, threadSafe = true, requiresDependencyResolution = ResolutionScope.COMPILE) public static class WithoutRuntimeDependencies extends ForProductionTypes { @Override protected List resolveClassPathElements(Map coordinates) throws MojoFailureException { try { return project.getCompileClasspathElements(); } catch (DependencyResolutionRequiredException exception) { throw new MojoFailureException("Could not resolve class path", exception); } } } /** * A Byte Buddy plugin that transforms a project's production class files where runtime class * path elements are included. */ @Mojo(name = "transform-runtime", defaultPhase = LifecyclePhase.PROCESS_CLASSES, threadSafe = true, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME) public static class WithRuntimeDependencies extends ForProductionTypes { @Override protected List resolveClassPathElements(Map coordinates) { try { return project.getRuntimeClasspathElements(); } catch (DependencyResolutionRequiredException exception) { throw new RuntimeException("Could not resolve runtime class path", exception); } } } /** * A Byte Buddy plugin that transforms a project's production class files where all scopes but the test scope are included. */ @Mojo(name = "transform-extended", defaultPhase = LifecyclePhase.PROCESS_CLASSES, threadSafe = true, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME) public static class WithExtendedDependencies extends ForProductionTypes { @Override protected List resolveClassPathElements(Map coordinates) { List classPath = new ArrayList(project.getArtifacts().size() + 1); String directory = project.getBuild().getOutputDirectory(); if (directory != null) { classPath.add(directory); } for (Artifact artifact : project.getArtifacts()) { if (artifact.getArtifactHandler().isAddedToClasspath() && !Artifact.SCOPE_TEST.equals(artifact.getScope()) && !Artifact.SCOPE_IMPORT.equals(artifact.getScope())) { File file = artifact.getFile(); if (file != null) { classPath.add(file.getPath()); } } } return classPath; } } } /** * A Byte Buddy plugin that transforms a project's test class files. */ @Mojo(name = "transform-test", defaultPhase = LifecyclePhase.PROCESS_TEST_CLASSES, threadSafe = true, requiresDependencyResolution = ResolutionScope.TEST) public static class ForTestTypes extends ForLifecycleTypes { @Override protected String getOutputDirectory() { return project.getBuild().getTestOutputDirectory(); } @MaybeNull @Override protected String getSourceDirectory() { return project.getBuild().getTestSourceDirectory(); } @Override protected List resolveClassPathElements(Map coordinates) throws MojoFailureException { try { return project.getTestClasspathElements(); } catch (DependencyResolutionRequiredException exception) { throw new MojoFailureException("Could not resolve test class path", exception); } } } } /** * Transforms specified classes from files in a folder or a jar file to a folder or jar file. */ @Mojo(name = "transform-location-empty", defaultPhase = LifecyclePhase.PROCESS_CLASSES, threadSafe = true) public static class ForExplicitLocations extends ByteBuddyMojo { /** * The source folder or jar file that should be transformed. */ @UnknownNull @Parameter(required = true) public String source; /** * The target folder or jar file to where the transformed sources should be written to. */ @UnknownNull @Parameter(required = true) public String target; /** * A list of dependencies to be included when resolving class files, additionally to the source jar. */ @MaybeNull @Parameter public List dependencies; @Override protected List resolveClassPathElements(Map coordinates) throws MojoExecutionException, MojoFailureException { List classPath = new ArrayList(); classPath.add(source); classPath.addAll(resolveImplicitClassPathElements()); if (dependencies != null && !dependencies.isEmpty()) { RepositorySystemSession repositorySystemSession = this.repositorySystemSession == null ? MavenRepositorySystemUtils.newSession() : this.repositorySystemSession; for (CoordinateConfiguration dependency : dependencies) { String managed = coordinates.get(new Coordinate(dependency.getGroupId(project.getGroupId()), dependency.getArtifactId(project.getArtifactId()))); MavenCoordinate mavenCoordinate = dependency.asCoordinate(project.getGroupId(), project.getArtifactId(), managed == null ? project.getVersion() : managed, project.getPackaging()); try { DependencyNode root = repositorySystem.collectDependencies( repositorySystemSession, new CollectRequest(new org.eclipse.aether.graph.Dependency(mavenCoordinate.asArtifact(), "runtime"), project.getRemotePluginRepositories())).getRoot(); repositorySystem.resolveDependencies(repositorySystemSession, new DependencyRequest().setRoot(root)); PreorderNodeListGenerator preorderNodeListGenerator = new PreorderNodeListGenerator(); root.accept(preorderNodeListGenerator); for (org.eclipse.aether.artifact.Artifact artifact : preorderNodeListGenerator.getArtifacts(false)) { classPath.add(artifact.getFile().toString()); } } catch (DependencyCollectionException exception) { throw new MojoExecutionException("Could not collect dependencies for " + mavenCoordinate, exception); } catch (DependencyResolutionException exception) { throw new MojoFailureException("Could not resolve dependencies for " + mavenCoordinate, exception); } } } return classPath; } /** * Resolves any implicit dependencies that should be added to the class path. * * @return The class path elements of the relevant output directory. * @throws MojoFailureException If the class loader resolution yields a failure. */ protected List resolveImplicitClassPathElements() throws MojoFailureException { return Collections.emptyList(); } @Override protected void apply(List transformers, List elements, Map coordinates) throws MojoExecutionException, MojoFailureException, IOException { File source = new File(this.source), target = new File(this.target); getLog().info("Transforming " + this.source + " to " + this.target); Plugin.Engine.Source resolved; if (source.isDirectory()) { resolved = new Plugin.Engine.Source.ForFolder(source); } else if (source.exists()) { resolved = new Plugin.Engine.Source.ForJarFile(source); } else { throw new MojoFailureException("Source location does not exist: " + source); } transform(elements, coordinates, transformers, resolved, target.isDirectory() ? new Plugin.Engine.Target.ForFolder(target) : new Plugin.Engine.Target.ForJarFile(target), source, false); } /** * Transforms specified classes from files in a folder or a jar file to a folder or jar file. Additionally, all class path dependencies * will be made visible during plugin application. */ @Mojo(name = "transform-location", defaultPhase = LifecyclePhase.PROCESS_CLASSES, threadSafe = true, requiresDependencyResolution = ResolutionScope.COMPILE) public static class WithoutRuntimeDependencies extends ForExplicitLocations { @Override protected List resolveImplicitClassPathElements() throws MojoFailureException { try { return project.getCompileClasspathElements(); } catch (DependencyResolutionRequiredException exception) { throw new MojoFailureException("Could not resolve class path", exception); } } } /** * Transforms specified classes from files in a folder or a jar file to a folder or jar file. Additionally, all class path dependencies * will be made visible during plugin application, including runtime dependencies. */ @Mojo(name = "transform-location-runtime", defaultPhase = LifecyclePhase.PROCESS_CLASSES, threadSafe = true, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME) public static class WithRuntimeDependencies extends ForExplicitLocations { @Override protected List resolveImplicitClassPathElements() { try { return project.getRuntimeClasspathElements(); } catch (DependencyResolutionRequiredException exception) { throw new RuntimeException("Could not resolve runtime class path", exception); } } } /** * Transforms specified classes from files in a folder or a jar file to a folder or jar file. Additionally, all class path dependencies * will be made visible during plugin application, including any non-test dependencies. */ @Mojo(name = "transform-location-extended", defaultPhase = LifecyclePhase.PROCESS_CLASSES, threadSafe = true, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME) public static class WithExtendedDependencies extends ForExplicitLocations { @Override protected List resolveImplicitClassPathElements() { List classPath = new ArrayList(project.getArtifacts().size() + 1); String directory = project.getBuild().getOutputDirectory(); if (directory != null) { classPath.add(directory); } for (Artifact artifact : project.getArtifacts()) { if (artifact.getArtifactHandler().isAddedToClasspath() && !Artifact.SCOPE_TEST.equals(artifact.getScope()) && !Artifact.SCOPE_IMPORT.equals(artifact.getScope())) { File file = artifact.getFile(); if (file != null) { classPath.add(file.getPath()); } } } return classPath; } } /** * Transforms specified classes from files in a folder or a jar file to a folder or jar file. Additionally, all class path dependencies * will be made visible during plugin application, including any non-test and test dependencies. */ @Mojo(name = "transform-location-test", defaultPhase = LifecyclePhase.PROCESS_TEST_CLASSES, threadSafe = true, requiresDependencyResolution = ResolutionScope.TEST) public static class ForTestTypes extends ForExplicitLocations { @Override protected List resolveImplicitClassPathElements() throws MojoFailureException { try { return project.getTestClasspathElements(); } catch (DependencyResolutionRequiredException exception) { throw new MojoFailureException("Could not resolve test class path", exception); } } } } @Mojo(name = "transform-dependencies", defaultPhase = LifecyclePhase.PROCESS_CLASSES, threadSafe = true, requiresDependencyResolution = ResolutionScope.COMPILE) public static class ForDependencyFolder extends ByteBuddyMojo { /** * The source folder that contains the project's dependencies. */ @UnknownNull @Parameter(required = true) public String source; /** * The target folder that contains the project's dependencies or {@code null} if the {@link ForDependencyFolder#source} folder should be used. */ @MaybeNull @Parameter(required = false) public String target; @Override protected List resolveClassPathElements(Map coordinates) { try { return project.getCompileClasspathElements(); } catch (DependencyResolutionRequiredException exception) { throw new RuntimeException("Could not resolve class path", exception); } } @Override protected void apply(List transformers, List elements, Map coordinates) throws MojoExecutionException, MojoFailureException, IOException { File source = new File(this.source), target = this.target == null ? source : new File(this.target); getLog().info("Transforming dependencies in " + this.source + (this.target == null ? "" : (" to " + this.target))); if (!source.isDirectory()) { throw new MojoFailureException("Expected " + this.source + " to be a folder"); } else if (this.target != null && target.isFile()) { throw new MojoFailureException("Did not expect " + this.target + " to be a file"); } File[] file = source.listFiles(); if (file != null) { for (File aFile : file) { if (aFile.isFile()) { transform(elements, coordinates, transformers, new Plugin.Engine.Source.ForJarFile(aFile), new Plugin.Engine.Target.ForJarFile(new File(target, aFile.getName())), aFile, false); } } } } } /** * A {@link BuildLogger} implementation for a Maven {@link Log}. */ protected static class MavenBuildLogger implements BuildLogger { /** * The logger to delegate to. */ private final Log log; /** * Creates a new Maven build logger. * * @param log The logger to delegate to. */ protected MavenBuildLogger(Log log) { this.log = log; } /** * {@inheritDoc} */ public boolean isDebugEnabled() { return log.isDebugEnabled(); } /** * {@inheritDoc} */ public void debug(String message) { log.debug(message); } /** * {@inheritDoc} */ public void debug(String message, Throwable throwable) { log.debug(message, throwable); } /** * {@inheritDoc} */ public boolean isInfoEnabled() { return log.isInfoEnabled(); } /** * {@inheritDoc} */ public void info(String message) { log.info(message); } /** * {@inheritDoc} */ public void info(String message, Throwable throwable) { log.info(message, throwable); } /** * {@inheritDoc} */ public boolean isWarnEnabled() { return log.isWarnEnabled(); } /** * {@inheritDoc} */ public void warn(String message) { log.warn(message); } /** * {@inheritDoc} */ public void warn(String message, Throwable throwable) { log.warn(message, throwable); } /** * {@inheritDoc} */ public boolean isErrorEnabled() { return log.isErrorEnabled(); } /** * {@inheritDoc} */ public void error(String message) { log.error(message); } /** * {@inheritDoc} */ public void error(String message, Throwable throwable) { log.error(message, throwable); } } /** * A {@link Plugin.Engine.Listener} that logs several relevant events during the build. */ protected static class TransformationLogger extends Plugin.Engine.Listener.Adapter { /** * The logger to delegate to. */ private final Log log; /** * Creates a new transformation logger. * * @param log The logger to delegate to. */ protected TransformationLogger(Log log) { this.log = log; } @Override public void onTransformation(TypeDescription typeDescription, List plugins) { log.debug("Transformed " + typeDescription + " using " + plugins); } @Override public void onError(TypeDescription typeDescription, Plugin plugin, Throwable throwable) { log.warn("Failed to transform " + typeDescription + " using " + plugin, throwable); } @Override public void onError(Map> throwables) { log.warn("Failed to transform " + throwables.size() + " types"); } @Override public void onError(Plugin plugin, Throwable throwable) { log.error("Failed to close " + plugin, throwable); } @Override public void onLiveInitializer(TypeDescription typeDescription, TypeDescription definingType) { log.debug("Discovered live initializer for " + definingType + " as a result of transforming " + typeDescription); } } /** * A transformer that is applied during the plugin's execution. */ protected abstract static class Transformer { /** * Returns the name of the plugin to apply. * * @return The name of the plugin to apply. * @throws MojoExecutionException If the plugin name was not set. */ protected abstract String getPlugin() throws MojoExecutionException; /** * Returns the argument resolvers to use. * * @return The argument resolvers to use. */ protected abstract List toArgumentResolvers(); /** * Resolves the class loader to use for resolving the plugin. * * @param classLoaderResolver The class loader resolver to use. * @param coordinates The managed coordinates of this project. * @param groupId The group id of this project. * @param artifactId The artifact id of this project. * @param version The version of this project. * @param packaging The packaging of this project. * @return The class loader to use. * @throws MojoFailureException If the class loader resolution yields a failure. * @throws MojoExecutionException The class loader resolution is incorrect. */ protected abstract ClassLoader toClassLoader(ClassLoaderResolver classLoaderResolver, Map coordinates, String groupId, String artifactId, String version, String packaging) throws MojoFailureException, MojoExecutionException; /** * A transformer for an explicitly configured plugin. */ protected static class ForConfiguredPlugin extends Transformer { /** * The configured transformation. */ private final Transformation transformation; /** * Creates a new transformer for an explicitly configured plugin. * * @param transformation The configured transformation. */ protected ForConfiguredPlugin(Transformation transformation) { this.transformation = transformation; } @Override protected String getPlugin() throws MojoExecutionException { return transformation.getPlugin(); } @Override protected List toArgumentResolvers() { return transformation.makeArgumentResolvers(); } @Override protected ClassLoader toClassLoader(ClassLoaderResolver classLoaderResolver, Map coordinates, String groupId, String artifactId, String version, String packaging) throws MojoFailureException, MojoExecutionException { String managed = coordinates.get(new Coordinate(transformation.getGroupId(groupId), transformation.getArtifactId(artifactId))); return classLoaderResolver.resolve(transformation.asCoordinate(groupId, artifactId, managed == null ? version : managed, packaging)); } } /** * A transformer for a discovered plugin. */ protected static class ForDiscoveredPlugin extends Transformer { /** * The name of the discovered plugin. */ private final String plugin; /** * Creates a new transformer for a discovered plugin. * * @param plugin The name of the discovered plugin. */ protected ForDiscoveredPlugin(String plugin) { this.plugin = plugin; } @Override protected String getPlugin() { return plugin; } @Override protected List toArgumentResolvers() { return Collections.emptyList(); } @Override protected ClassLoader toClassLoader(ClassLoaderResolver classLoaderResolver, Map coordinates, String groupId, String artifactId, String version, String packaging) { return ByteBuddyMojo.class.getClassLoader(); } /** * A transformer for a discovered plugin from the class path. */ protected static class FromClassLoader extends ForDiscoveredPlugin { /** * The class path elements for loading this plugin. */ private final List classPath; /** * Creates a new transformer for a discovered plugin from the class path. * * @param plugin The name of the discovered plugin. * @param classPath The class path elements for loading this plugin. */ protected FromClassLoader(String plugin, List classPath) { super(plugin); this.classPath = classPath; } @Override @SuppressFBWarnings(value = "DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED", justification = "The security manager is not normally used within Maven") protected ClassLoader toClassLoader(ClassLoaderResolver classLoaderResolver, Map coordinates, String groupId, String artifactId, String version, String packaging) { URL[] url = new URL[classPath.size()]; for (int index = 0; index < classPath.size(); index++) { try { url[index] = new File(classPath.get(index)).toURI().toURL(); } catch (MalformedURLException exception) { throw new IllegalStateException("Failed to resolve class path element to URL: " + classPath.get(index), exception); } } return new URLClassLoader(url, ByteBuddyMojo.class.getClassLoader()); } } } } /** * A coordinate to locate a managed dependency. */ protected static class Coordinate { /** * The managed dependency's group id. */ private final String groupId; /** * The managed dependency's artifact id. */ private final String artifactId; /** * Creates a new coordinate for a managed dependency. * * @param groupId The managed depencency's group id. * @param artifactId The managed depencency's artifact id. */ protected Coordinate(String groupId, String artifactId) { this.groupId = groupId; this.artifactId = artifactId; } @Override public int hashCode() { int result = groupId.hashCode(); result = 31 * result + artifactId.hashCode(); return result; } @Override public boolean equals(Object other) { if (this == other) return true; if (other == null || getClass() != other.getClass()) return false; Coordinate that = (Coordinate) other; if (!groupId.equals(that.groupId)) return false; return artifactId.equals(that.artifactId); } } /** * A filter for files that were written before a given timestamp, to avoid duplicate application. */ protected static class StalenessFilter extends ElementMatcher.Junction.ForNonNullValues { /** * The logger to use. */ private final Log log; /** * The timestamp for files to be filtered if they were created before it. */ private final long latestTimestamp; /** * A count of class files that were filtered. */ private int filtered; /** * Creates a new staleness filter. * * @param log The logger to use. * @param latestTimestamp The timestamp for files to be filtered if they were created before it. */ protected StalenessFilter(Log log, long latestTimestamp) { this.log = log; this.latestTimestamp = latestTimestamp; } /** * {@inheritDoc} */ protected boolean doMatch(Plugin.Engine.Source.Element target) { File file = target.resolveAs(File.class); if (file == null) { throw new IllegalStateException("Expected " + target + " to resolve to a file"); } if (file.lastModified() < latestTimestamp) { filtered += 1; log.debug("Filtering " + file + " due to staleness: " + file.lastModified()); return false; } else { return true; } } /** * Returns a count of class files that were filtered as they were created prior to the last build. * * @return The amount of filtered classes. */ protected int getFiltered() { return filtered; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy