Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Maven agent modes support work #315

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion native-maven-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,11 @@ dependencies {
implementation(libs.jackson.databind)
implementation(libs.jvmReachabilityMetadata)
implementation(libs.graalvm.svm)
implementation(libs.maven.pluginAnnotations)

compileOnly(libs.maven.pluginApi)
compileOnly(libs.maven.core)
compileOnly(libs.maven.artifact)
compileOnly(libs.maven.pluginAnnotations)

mavenEmbedder(libs.maven.embedder)
mavenEmbedder(libs.maven.aether.connector)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
import org.graalvm.buildtools.Utils;
import org.graalvm.buildtools.maven.config.ExcludeConfigConfiguration;
import org.graalvm.buildtools.maven.config.MetadataRepositoryConfiguration;
import org.graalvm.buildtools.maven.config.agent.AgentConfiguration;
import org.graalvm.buildtools.utils.FileUtils;
import org.graalvm.buildtools.utils.NativeImageUtils;
import org.graalvm.buildtools.utils.SharedConstants;
Expand Down Expand Up @@ -183,6 +184,9 @@ public abstract class AbstractNativeMojo extends AbstractMojo {
@Parameter(property = NATIVE_IMAGE_DRY_RUN, defaultValue = "false")
protected boolean dryRun;

@Parameter(property = "agent")
protected AgentConfiguration agent;

protected GraalVMReachabilityMetadataRepository metadataRepository;

@Component
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import org.codehaus.plexus.logging.Logger;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.graalvm.buildtools.Utils;
import org.graalvm.buildtools.maven.config.ExtensionTimeConfigurationUtility;
import org.graalvm.buildtools.utils.SharedConstants;

import java.io.File;
Expand Down Expand Up @@ -172,6 +173,10 @@ public void afterProjectsRead(MavenSession session) {
})
);
updatePluginConfiguration(nativePlugin, (exec, configuration) -> {
AbstractNativeMojo mojo = new NativeCompileMojo();
ExtensionTimeConfigurationUtility.loadFromXml(configuration, mojo);
ExtensionTimeConfigurationUtility.debugPrint(mojo);
System.out.println("HERE!");
Context context = exec.getGoals().stream().anyMatch("test"::equals) ? Context.test : Context.main;
Xpp3Dom agentResourceDirectory = findOrAppend(configuration, "agentResourceDirectory");
agentResourceDirectory.setValue(agentOutputDirectoryFor(target, context));
Expand Down Expand Up @@ -204,6 +209,7 @@ private static void withPlugin(Build build, String artifactId, Consumer<? super
private static boolean isAgentEnabled(MavenSession session, Plugin nativePlugin) {
String systemProperty = session.getSystemProperties().getProperty("agent");
if (systemProperty != null) {
System.clearProperty("agent");
// -Dagent=[true|false] overrides configuration in the POM.
return parseBoolean("agent system property", systemProperty);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
package org.graalvm.buildtools.maven.config;

import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.apache.maven.plugin.descriptor.PluginDescriptorBuilder;
import org.apache.maven.plugins.annotations.Parameter;
import org.codehaus.plexus.configuration.PlexusConfigurationException;
import org.codehaus.plexus.util.ReaderFactory;
import org.codehaus.plexus.util.xml.Xpp3Dom;

import java.beans.PropertyEditor;
import java.beans.PropertyEditorManager;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* This is utility class that provides a way to parse and/or persist configuration options
* during the extension time.
* Normally in Maven, the parameters are bound from XML to values during the Mojo creation and there is no way
* to access / modify those values other than manually traversing DOM tree.
* .
* TBH, this should probably be a part of a separate utility library.
*/
public class ExtensionTimeConfigurationUtility {

public final String PLUGIN_DESCRIPTOR_LOCATION = "/META-INF/maven/plugin.xml";
public final PluginDescriptorBuilder builder = new PluginDescriptorBuilder();

/**
* As Maven loves to complicate things, the @Parameter annotation isn't preserved until runtime :)
* The idea is that there is а plugin descriptor XML file that is generated during plugin publishing and embedded into
* each plugin jar. That file - when parsed - is later used (among other things) to map `pom.xml` parameters to Mojos.
*
* @return plugin descriptor
*/
public PluginDescriptor parsePluginDescriptor() {
try (InputStream xmlDescriptor = ClassLoader.class.getResourceAsStream(PLUGIN_DESCRIPTOR_LOCATION)) {
Reader reader = ReaderFactory.newXmlReader(xmlDescriptor);
return builder.build(reader, null);
} catch (IOException | PlexusConfigurationException e) {
throw new RuntimeException(e);
}
}

public Object populateMojo(Xpp3Dom configuration, Class<?> mojoClass) {
PluginDescriptor pd = parsePluginDescriptor();
MojoDescriptor descriptor = pd.getMojos().stream()
.filter(mojoDescriptor ->
mojoDescriptor.getImplementationClass().getCanonicalName().equals(mojoClass.getCanonicalName()))
.findFirst().orElse(null);
if (descriptor == null) {
return null;
}

Object instance;
try {
instance = mojoClass.getConstructor().newInstance();
} catch (InstantiationException | IllegalAccessException |
InvocationTargetException | NoSuchMethodException e) {
throw new RuntimeException(e);
}

Map<String, org.apache.maven.plugin.descriptor.Parameter> paramMap
= descriptor.getParameterMap();

getAllFields(mojoClass).forEach(field -> {
if (!paramMap.containsKey(field.getName())) {
return;
}

field.setAccessible(true);

/*
TODO:
... aaand here we should try to map types from the plugin descriptor to the fields
and then map everything to the plugin configuration block, and then convert all the
values from the plugin configuration block to their respective types (possibly using
similar idea as the one present in the `loadFromXml` method)...
*/

});
return instance;
}

/**
* Given XML configuration and class instance injects values in @Parameter annotated fields.
* .
* IGNORE THIS METHOD. This approach doesn't work since Parameter annotation isn't retained
* until the runtime... I didn't remove this since it might contain some useful snippets for
* implementation of the `populateMojo` method.
*
* @param configuration XML configuration node
* @param instance class instance
*/
public static void loadFromXml(Xpp3Dom configuration, Object instance) {
getAllFields(instance.getClass()).forEach(field -> {
Parameter annotation = field.getAnnotation(Parameter.class);
if (annotation == null) {
return;
}

String property = annotation.property().isEmpty() ? field.getName() : annotation.property();
String alias = annotation.alias();
Xpp3Dom element = configuration.getChild(property);
if (element == null && alias != null) {
element = configuration.getChild(alias);
}
if (element == null) {
return;
}

field.setAccessible(true);

if (element.getChildCount() == 0) {
// Now we've found the value, so we need to convert it, and inject it into field of a corresponding class.
injectValueToField(element, instance, field);
return;
}

if (List.class.isAssignableFrom(field.getType())) {
ParameterizedType listType = (ParameterizedType) field.getGenericType();
Class<?> listElementType = (Class<?>) listType.getActualTypeArguments()[0];

ArrayList<Object> list = new ArrayList<>();
Arrays.stream(element.getChildren()).forEach(entry -> {
list.add(convertFromString(listElementType, entry.getValue()));
});
setFieldValue(instance, field, list);
} else if (Map.class.isAssignableFrom(field.getType())) {
ParameterizedType mapType = (ParameterizedType) field.getGenericType();
Class<?> mapKeyType = (Class<?>) mapType.getActualTypeArguments()[0];
Class<?> mapValueType = (Class<?>) mapType.getActualTypeArguments()[1];

HashMap<Object, Object> map = new HashMap<>();
Arrays.stream(element.getChildren()).forEach(entry -> {
map.put(convertFromString(mapKeyType, entry.getName()),
convertFromString(mapValueType, entry.getValue()));
});
setFieldValue(instance, field, map);
} else {
// This is probably a complex type, so we should try going in recursively.
try {
Object nested = field.getType().getConstructor().newInstance();
loadFromXml(element, nested);
setFieldValue(instance, field, nested);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException |
NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
});
}

/**
* Sets a field in a given instance to a given value
*
* @param instance target instance
* @param field field of instance class
* @param value value to be set
*/
public static void setFieldValue(Object instance, Field field, Object value) {
try {
field.set(instance, value);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}

/**
* A utility method that consumes a string and returns an object of a given class.
*
* @param targetType class for resulting object
* @param text string content
* @return resulting object
*/
public static Object convertFromString(Class<?> targetType, String text) {
// Yes, we are using java.beans utility classes here.
PropertyEditor editor = PropertyEditorManager.findEditor(targetType);
if (editor == null) {
if (File.class.isAssignableFrom(targetType)) {
return Paths.get(text).toFile();
}
if (Path.class.isAssignableFrom(targetType)) {
return Paths.get(text);
}
if (URL.class.isAssignableFrom(targetType)) {
try {
return new URL(text);
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
return null;
}
editor.setAsText(text);
return editor.getValue();
}

/**
* Given XML element, instance and its field, injects converted value into field.
*
* @param element XML node containing string
* @param instance object of that class
* @param field field in
*/
public static void injectValueToField(Xpp3Dom element, Object instance, Field field) {
field.setAccessible(true);
Object parsed = convertFromString(field.getType(), element.getValue());
if (parsed != null) {
setFieldValue(instance, field, parsed);
}
}

private static List<Field> getAllFields(Class<?> target) {
List<Field> fields = new ArrayList<>();
Class<?> clazz = target;
while (clazz != Object.class) {
fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
clazz = clazz.getSuperclass();
}
return fields;
}

public static void debugPrint(Object instance) {
System.out.println("IN HERE " + instance.getClass().getCanonicalName());
getAllFields(instance.getClass()).forEach(field -> {
System.out.println("GET FIELD: " + field.getName());
Parameter annotation = field.getAnnotation(Parameter.class);
if (annotation == null) {
System.out.println("skipped" + field.getAnnotations().length);
Arrays.stream(field.getAnnotations()).forEach(System.out::println);
return;
}
try {
System.out.println(field.getName() + ": " + field.get(instance));
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
});
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package org.graalvm.buildtools.maven.config.agent;

import org.apache.maven.plugins.annotations.Parameter;

import java.util.List;

public class AgentConfiguration {
@Parameter(property = "enabled", defaultValue = "false")
protected boolean enabled;

@Parameter(property = "defaultMode", defaultValue = "standard")
protected String defaultMode;

@Parameter(property = "disabledPhases")
protected List<String> disabledPhases;

@Parameter(property = "modes")
protected AgentModeConfiguration modes;

public boolean isEnabled() {
return enabled;
}

public void setEnabled(boolean enabled) {
this.enabled = enabled;
}

public String getDefaultMode() {
return defaultMode;
}

public void setDefaultMode(String defaultMode) {
this.defaultMode = defaultMode;
}

public List<String> getDisabledPhases() {
return disabledPhases;
}

public void setDisabledPhases(List<String> disabledPhases) {
this.disabledPhases = disabledPhases;
}

public AgentModeConfiguration getModes() {
return modes;
}

public void setModes(AgentModeConfiguration modes) {
this.modes = modes;
}
}
Loading