Speed up parallel plugin setup, fixes #310

This commit is contained in:
Guillaume Nodet
2021-01-14 14:59:30 +01:00
committed by GitHub
16 changed files with 1295 additions and 2 deletions

View File

@@ -71,6 +71,7 @@ import org.apache.maven.extension.internal.CoreExtensionEntry;
import org.apache.maven.lifecycle.LifecycleExecutionException;
import org.apache.maven.model.building.ModelProcessor;
import org.apache.maven.plugin.ExtensionRealmCache;
import org.apache.maven.plugin.MavenPluginManager;
import org.apache.maven.plugin.PluginArtifactsCache;
import org.apache.maven.plugin.PluginRealmCache;
import org.apache.maven.project.MavenProject;
@@ -102,6 +103,7 @@ import org.mvndaemon.mvnd.logging.internal.Slf4jLoggerManager;
import org.mvndaemon.mvnd.logging.smart.BuildEventListener;
import org.mvndaemon.mvnd.logging.smart.LoggingExecutionListener;
import org.mvndaemon.mvnd.logging.smart.LoggingOutputStream;
import org.mvndaemon.mvnd.plugin.CliMavenPluginManager;
import org.mvndaemon.mvnd.transfer.DaemonMavenTransferListener;
import org.slf4j.ILoggerFactory;
import org.slf4j.Logger;
@@ -505,6 +507,7 @@ public class DaemonMavenCli {
bind(PluginArtifactsCache.class).to(CliPluginArtifactsCache.class);
bind(PluginRealmCache.class).to(CliPluginRealmCache.class);
bind(ProjectArtifactsCache.class).to(CliProjectArtifactsCache.class);
bind(MavenPluginManager.class).to(CliMavenPluginManager.class);
}
});

View File

@@ -16,6 +16,7 @@
package org.mvndaemon.mvnd.cache.factory;
import java.util.function.BiPredicate;
import java.util.function.Function;
/**
* Cache containing records that can be invalidated.
@@ -53,4 +54,9 @@ public interface Cache<K, V extends CacheRecord> {
*/
void removeIf(BiPredicate<K, V> predicate);
/**
* Get or compute the cached value if absent and return it.
*/
V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction);
}

View File

@@ -26,6 +26,7 @@ import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.inject.Named;
import javax.inject.Singleton;
@@ -145,5 +146,22 @@ public class TimestampCacheFactory extends AbstractLogEnabled implements CacheFa
}
}
@Override
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
return map.compute(key, (k, v) -> {
if (v != null) {
try {
if (Objects.equals(v.timestamp, v.current())) {
return v;
}
} catch (RuntimeException e) {
// ignore and invalidate the record
}
v.record.invalidate();
v = null;
}
return new Record<>(mappingFunction.apply(k));
}).record;
}
}
}

View File

@@ -29,6 +29,7 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiPredicate;
import java.util.function.Function;
import javax.inject.Named;
import javax.inject.Singleton;
import org.codehaus.plexus.logging.AbstractLogEnabled;
@@ -241,5 +242,14 @@ public class WatchServiceCacheFactory extends AbstractLogEnabled implements Cach
}
}
@Override
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
validateRecords();
return map.computeIfAbsent(key, k -> {
V v = mappingFunction.apply(k);
add(v);
return v;
});
}
}
}

View File

@@ -25,6 +25,9 @@ import javax.inject.Named;
import javax.inject.Singleton;
import org.apache.maven.model.Plugin;
import org.apache.maven.plugin.DefaultPluginDescriptorCache;
import org.apache.maven.plugin.InvalidPluginDescriptorException;
import org.apache.maven.plugin.PluginDescriptorParsingException;
import org.apache.maven.plugin.PluginResolutionException;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.codehaus.plexus.classworlds.realm.NoSuchRealmException;
@@ -38,6 +41,12 @@ import org.mvndaemon.mvnd.cache.factory.CacheRecord;
@Named
public class CliPluginDescriptorCache extends DefaultPluginDescriptorCache {
@FunctionalInterface
public interface PluginDescriptorSupplier {
PluginDescriptor load()
throws PluginResolutionException, PluginDescriptorParsingException, InvalidPluginDescriptorException;
}
protected static class Record implements CacheRecord {
private final PluginDescriptor descriptor;
@@ -81,6 +90,31 @@ public class CliPluginDescriptorCache extends DefaultPluginDescriptorCache {
return r != null ? clone(r.descriptor) : null;
}
public PluginDescriptor get(Key key, PluginDescriptorSupplier supplier)
throws PluginDescriptorParsingException, PluginResolutionException, InvalidPluginDescriptorException {
try {
Record r = cache.computeIfAbsent(key, k -> {
try {
return new Record(clone(supplier.load()));
} catch (PluginDescriptorParsingException | PluginResolutionException | InvalidPluginDescriptorException e) {
throw new RuntimeException(e);
}
});
return clone(r.descriptor);
} catch (RuntimeException e) {
if (e.getCause() instanceof PluginDescriptorParsingException) {
throw (PluginDescriptorParsingException) e.getCause();
}
if (e.getCause() instanceof PluginResolutionException) {
throw (PluginResolutionException) e.getCause();
}
if (e.getCause() instanceof InvalidPluginDescriptorException) {
throw (InvalidPluginDescriptorException) e.getCause();
}
throw e;
}
}
@Override
public void put(Key key, PluginDescriptor descriptor) {
cache.put(key, new Record(clone(descriptor)));

View File

@@ -23,7 +23,9 @@ import javax.inject.Named;
import javax.inject.Singleton;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.DefaultPluginRealmCache;
import org.apache.maven.plugin.PluginContainerException;
import org.apache.maven.plugin.PluginRealmCache;
import org.apache.maven.plugin.PluginResolutionException;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.codehaus.plexus.classworlds.realm.NoSuchRealmException;
@@ -38,6 +40,11 @@ import org.mvndaemon.mvnd.cache.factory.CacheFactory;
@Typed(PluginRealmCache.class)
public class CliPluginRealmCache extends DefaultPluginRealmCache {
@FunctionalInterface
public interface PluginRealmSupplier {
CacheRecord load() throws PluginResolutionException, PluginContainerException;
}
protected static class Record implements org.mvndaemon.mvnd.cache.factory.CacheRecord {
final CacheRecord record;
@@ -75,6 +82,28 @@ public class CliPluginRealmCache extends DefaultPluginRealmCache {
return r != null ? r.record : null;
}
public CacheRecord get(Key key, PluginRealmSupplier supplier)
throws PluginResolutionException, PluginContainerException {
try {
Record r = cache.computeIfAbsent(key, k -> {
try {
return new Record(supplier.load());
} catch (PluginResolutionException | PluginContainerException e) {
throw new RuntimeException(e);
}
});
return r.record;
} catch (RuntimeException e) {
if (e.getCause() instanceof PluginResolutionException) {
throw (PluginResolutionException) e.getCause();
}
if (e.getCause() instanceof PluginContainerException) {
throw (PluginContainerException) e.getCause();
}
throw e;
}
}
@Override
public CacheRecord put(Key key, ClassRealm pluginRealm, List<Artifact> pluginArtifacts) {
CacheRecord record = super.put(key, pluginRealm, pluginArtifacts);

View File

@@ -0,0 +1,796 @@
/*
* Copyright 2021 the original author or authors.
*
* 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 org.mvndaemon.mvnd.plugin;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.apache.maven.RepositoryUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.classrealm.ClassRealmManager;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.execution.scope.internal.MojoExecutionScopeModule;
import org.apache.maven.model.Plugin;
import org.apache.maven.monitor.logging.DefaultLog;
import org.apache.maven.plugin.ContextEnabled;
import org.apache.maven.plugin.DebugConfigurationListener;
import org.apache.maven.plugin.ExtensionRealmCache;
import org.apache.maven.plugin.InvalidPluginDescriptorException;
import org.apache.maven.plugin.MavenPluginManager;
import org.apache.maven.plugin.MavenPluginValidator;
import org.apache.maven.plugin.Mojo;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.plugin.MojoNotFoundException;
import org.apache.maven.plugin.PluginArtifactsCache;
import org.apache.maven.plugin.PluginConfigurationException;
import org.apache.maven.plugin.PluginContainerException;
import org.apache.maven.plugin.PluginDescriptorCache;
import org.apache.maven.plugin.PluginDescriptorParsingException;
import org.apache.maven.plugin.PluginIncompatibleException;
import org.apache.maven.plugin.PluginManagerException;
import org.apache.maven.plugin.PluginParameterException;
import org.apache.maven.plugin.PluginParameterExpressionEvaluator;
import org.apache.maven.plugin.PluginRealmCache;
import org.apache.maven.plugin.PluginResolutionException;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.apache.maven.plugin.descriptor.Parameter;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.apache.maven.plugin.descriptor.PluginDescriptorBuilder;
import org.apache.maven.plugin.internal.PluginDependenciesResolver;
import org.apache.maven.plugin.version.DefaultPluginVersionRequest;
import org.apache.maven.plugin.version.PluginVersionRequest;
import org.apache.maven.plugin.version.PluginVersionResolutionException;
import org.apache.maven.plugin.version.PluginVersionResolver;
import org.apache.maven.project.ExtensionDescriptor;
import org.apache.maven.project.ExtensionDescriptorBuilder;
import org.apache.maven.project.MavenProject;
import org.apache.maven.rtinfo.RuntimeInformation;
import org.apache.maven.session.scope.internal.SessionScopeModule;
import org.codehaus.plexus.DefaultPlexusContainer;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.codehaus.plexus.component.composition.CycleDetectedInComponentGraphException;
import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
import org.codehaus.plexus.component.configurator.ComponentConfigurator;
import org.codehaus.plexus.component.configurator.ConfigurationListener;
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
import org.codehaus.plexus.component.repository.ComponentDescriptor;
import org.codehaus.plexus.component.repository.exception.ComponentLifecycleException;
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
import org.codehaus.plexus.configuration.PlexusConfiguration;
import org.codehaus.plexus.configuration.PlexusConfigurationException;
import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
import org.codehaus.plexus.logging.Logger;
import org.codehaus.plexus.logging.LoggerManager;
import org.codehaus.plexus.util.ReaderFactory;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.graph.DependencyFilter;
import org.eclipse.aether.graph.DependencyNode;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.util.filter.AndDependencyFilter;
import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator;
import org.eclipse.sisu.Priority;
import org.eclipse.sisu.Typed;
import org.mvndaemon.mvnd.cache.impl.CliPluginDescriptorCache;
import org.mvndaemon.mvnd.cache.impl.CliPluginRealmCache;
/*
* gnodet: This file is based on maven DefaultMavenPluginManager and changed in order
* to better support parallel builds. See https://github.com/mvndaemon/mvnd/issues/310
*/
/**
* Provides basic services to manage Maven plugins and their mojos. This component is kept general in its design such
* that the plugins/mojos can be used in arbitrary contexts. In particular, the mojos can be used for ordinary build
* plugins as well as special purpose plugins like reports.
*
* @author Benjamin Bentmann
* @since 3.0
*/
@Singleton
@Named
@Priority(10)
@Typed(MavenPluginManager.class)
public class CliMavenPluginManager
implements MavenPluginManager {
/**
* <p>
* PluginId =&gt; ExtensionRealmCache.CacheRecord map MavenProject context value key. The map is used to ensure the
* same class realm is used to load build extensions and load mojos for extensions=true plugins.
* </p>
* <strong>Note:</strong> This is part of internal implementation and may be changed or removed without notice
*
* @since 3.3.0
*/
public static final String KEY_EXTENSIONS_REALMS = CliMavenPluginManager.class.getName() + "/extensionsRealms";
@Inject
private Logger logger;
@Inject
private LoggerManager loggerManager;
@Inject
private PlexusContainer container;
@Inject
private ClassRealmManager classRealmManager;
@Inject
private CliPluginDescriptorCache pluginDescriptorCache;
@Inject
private CliPluginRealmCache pluginRealmCache;
@Inject
private PluginDependenciesResolver pluginDependenciesResolver;
@Inject
private RuntimeInformation runtimeInformation;
@Inject
private ExtensionRealmCache extensionRealmCache;
@Inject
private PluginVersionResolver pluginVersionResolver;
@Inject
private PluginArtifactsCache pluginArtifactsCache;
private ExtensionDescriptorBuilder extensionDescriptorBuilder = new ExtensionDescriptorBuilder();
private PluginDescriptorBuilder builder = new PluginDescriptorBuilder();
public PluginDescriptor getPluginDescriptor(Plugin plugin, List<RemoteRepository> repositories,
RepositorySystemSession session)
throws PluginResolutionException, PluginDescriptorParsingException, InvalidPluginDescriptorException {
PluginDescriptorCache.Key cacheKey = pluginDescriptorCache.createKey(plugin, repositories, session);
PluginDescriptor pluginDescriptor = pluginDescriptorCache.get(cacheKey, () -> {
org.eclipse.aether.artifact.Artifact artifact = pluginDependenciesResolver.resolve(plugin, repositories, session);
Artifact pluginArtifact = RepositoryUtils.toArtifact(artifact);
PluginDescriptor descriptor = extractPluginDescriptor(pluginArtifact, plugin);
descriptor.setRequiredMavenVersion(artifact.getProperty("requiredMavenVersion", null));
return descriptor;
});
pluginDescriptor.setPlugin(plugin);
return pluginDescriptor;
}
private PluginDescriptor extractPluginDescriptor(Artifact pluginArtifact, Plugin plugin)
throws PluginDescriptorParsingException, InvalidPluginDescriptorException {
PluginDescriptor pluginDescriptor = null;
File pluginFile = pluginArtifact.getFile();
try {
if (pluginFile.isFile()) {
try (JarFile pluginJar = new JarFile(pluginFile, false)) {
ZipEntry pluginDescriptorEntry = pluginJar.getEntry(getPluginDescriptorLocation());
if (pluginDescriptorEntry != null) {
InputStream is = pluginJar.getInputStream(pluginDescriptorEntry);
pluginDescriptor = parsePluginDescriptor(is, plugin, pluginFile.getAbsolutePath());
}
}
} else {
File pluginXml = new File(pluginFile, getPluginDescriptorLocation());
if (pluginXml.isFile()) {
try (InputStream is = new BufferedInputStream(new FileInputStream(pluginXml))) {
pluginDescriptor = parsePluginDescriptor(is, plugin, pluginXml.getAbsolutePath());
}
}
}
if (pluginDescriptor == null) {
throw new IOException("No plugin descriptor found at " + getPluginDescriptorLocation());
}
} catch (IOException e) {
throw new PluginDescriptorParsingException(plugin, pluginFile.getAbsolutePath(), e);
}
MavenPluginValidator validator = new MavenPluginValidator(pluginArtifact);
validator.validate(pluginDescriptor);
if (validator.hasErrors()) {
throw new InvalidPluginDescriptorException(
"Invalid plugin descriptor for " + plugin.getId() + " (" + pluginFile + ")", validator.getErrors());
}
pluginDescriptor.setPluginArtifact(pluginArtifact);
return pluginDescriptor;
}
private String getPluginDescriptorLocation() {
return "META-INF/maven/plugin.xml";
}
private PluginDescriptor parsePluginDescriptor(InputStream is, Plugin plugin, String descriptorLocation)
throws PluginDescriptorParsingException {
try {
Reader reader = ReaderFactory.newXmlReader(is);
PluginDescriptor pluginDescriptor = builder.build(reader, descriptorLocation);
return pluginDescriptor;
} catch (IOException | PlexusConfigurationException e) {
throw new PluginDescriptorParsingException(plugin, descriptorLocation, e);
}
}
public MojoDescriptor getMojoDescriptor(Plugin plugin, String goal, List<RemoteRepository> repositories,
RepositorySystemSession session)
throws MojoNotFoundException, PluginResolutionException, PluginDescriptorParsingException,
InvalidPluginDescriptorException {
PluginDescriptor pluginDescriptor = getPluginDescriptor(plugin, repositories, session);
MojoDescriptor mojoDescriptor = pluginDescriptor.getMojo(goal);
if (mojoDescriptor == null) {
throw new MojoNotFoundException(goal, pluginDescriptor);
}
return mojoDescriptor;
}
public void checkRequiredMavenVersion(PluginDescriptor pluginDescriptor)
throws PluginIncompatibleException {
String requiredMavenVersion = pluginDescriptor.getRequiredMavenVersion();
if (StringUtils.isNotBlank(requiredMavenVersion)) {
try {
if (!runtimeInformation.isMavenVersion(requiredMavenVersion)) {
throw new PluginIncompatibleException(pluginDescriptor.getPlugin(),
"The plugin " + pluginDescriptor.getId()
+ " requires Maven version " + requiredMavenVersion);
}
} catch (RuntimeException e) {
logger.warn("Could not verify plugin's Maven prerequisite: " + e.getMessage());
}
}
}
public void setupPluginRealm(PluginDescriptor pluginDescriptor, MavenSession session,
ClassLoader parent, List<String> imports, DependencyFilter filter)
throws PluginResolutionException, PluginContainerException {
Plugin plugin = pluginDescriptor.getPlugin();
MavenProject project = session.getCurrentProject();
if (plugin.isExtensions()) {
ExtensionRealmCache.CacheRecord extensionRecord;
try {
RepositorySystemSession repositorySession = session.getRepositorySession();
extensionRecord = setupExtensionsRealm(project, plugin, repositorySession);
} catch (PluginManagerException e) {
// extensions realm is expected to be fully setup at this point
// any exception means a problem in maven code, not a user error
throw new IllegalStateException(e);
}
ClassRealm pluginRealm = extensionRecord.getRealm();
List<Artifact> pluginArtifacts = extensionRecord.getArtifacts();
for (ComponentDescriptor<?> componentDescriptor : pluginDescriptor.getComponents()) {
componentDescriptor.setRealm(pluginRealm);
}
pluginDescriptor.setClassRealm(pluginRealm);
pluginDescriptor.setArtifacts(pluginArtifacts);
} else {
Map<String, ClassLoader> foreignImports = calcImports(project, parent, imports);
PluginRealmCache.Key cacheKey = pluginRealmCache.createKey(plugin, parent, foreignImports, filter,
project.getRemotePluginRepositories(),
session.getRepositorySession());
PluginRealmCache.CacheRecord cacheRecord = pluginRealmCache.get(cacheKey, () -> {
createPluginRealm(pluginDescriptor, session, parent, foreignImports, filter);
return new PluginRealmCache.CacheRecord(pluginDescriptor.getClassRealm(), pluginDescriptor.getArtifacts());
});
if (cacheRecord != null) {
pluginDescriptor.setClassRealm(cacheRecord.getRealm());
pluginDescriptor.setArtifacts(new ArrayList<>(cacheRecord.getArtifacts()));
for (ComponentDescriptor<?> componentDescriptor : pluginDescriptor.getComponents()) {
componentDescriptor.setRealm(cacheRecord.getRealm());
}
}
pluginRealmCache.register(project, cacheKey, cacheRecord);
}
}
private void createPluginRealm(PluginDescriptor pluginDescriptor, MavenSession session, ClassLoader parent,
Map<String, ClassLoader> foreignImports, DependencyFilter filter)
throws PluginResolutionException, PluginContainerException {
Plugin plugin = Objects.requireNonNull(pluginDescriptor.getPlugin(), "pluginDescriptor.plugin cannot be null");
Artifact pluginArtifact = Objects.requireNonNull(pluginDescriptor.getPluginArtifact(),
"pluginDescriptor.pluginArtifact cannot be null");
MavenProject project = session.getCurrentProject();
final ClassRealm pluginRealm;
final List<Artifact> pluginArtifacts;
RepositorySystemSession repositorySession = session.getRepositorySession();
DependencyFilter dependencyFilter = project.getExtensionDependencyFilter();
dependencyFilter = AndDependencyFilter.newInstance(dependencyFilter, filter);
DependencyNode root = pluginDependenciesResolver.resolve(plugin, RepositoryUtils.toArtifact(pluginArtifact),
dependencyFilter,
project.getRemotePluginRepositories(), repositorySession);
PreorderNodeListGenerator nlg = new PreorderNodeListGenerator();
root.accept(nlg);
pluginArtifacts = toMavenArtifacts(root, nlg);
pluginRealm = classRealmManager.createPluginRealm(plugin, parent, null, foreignImports,
toAetherArtifacts(pluginArtifacts));
discoverPluginComponents(pluginRealm, plugin, pluginDescriptor);
pluginDescriptor.setClassRealm(pluginRealm);
pluginDescriptor.setArtifacts(pluginArtifacts);
}
private void discoverPluginComponents(final ClassRealm pluginRealm, Plugin plugin,
PluginDescriptor pluginDescriptor)
throws PluginContainerException {
try {
if (pluginDescriptor != null) {
for (ComponentDescriptor<?> componentDescriptor : pluginDescriptor.getComponents()) {
componentDescriptor.setRealm(pluginRealm);
container.addComponentDescriptor(componentDescriptor);
}
}
((DefaultPlexusContainer) container).discoverComponents(pluginRealm, new SessionScopeModule(container),
new MojoExecutionScopeModule(container));
} catch (ComponentLookupException | CycleDetectedInComponentGraphException e) {
throw new PluginContainerException(plugin, pluginRealm,
"Error in component graph of plugin " + plugin.getId() + ": "
+ e.getMessage(),
e);
}
}
private List<org.eclipse.aether.artifact.Artifact> toAetherArtifacts(final List<Artifact> pluginArtifacts) {
return new ArrayList<>(RepositoryUtils.toArtifacts(pluginArtifacts));
}
private List<Artifact> toMavenArtifacts(DependencyNode root, PreorderNodeListGenerator nlg) {
List<Artifact> artifacts = new ArrayList<>(nlg.getNodes().size());
RepositoryUtils.toArtifacts(artifacts, Collections.singleton(root), Collections.<String> emptyList(), null);
for (Iterator<Artifact> it = artifacts.iterator(); it.hasNext();) {
Artifact artifact = it.next();
if (artifact.getFile() == null) {
it.remove();
}
}
return Collections.unmodifiableList(artifacts);
}
private Map<String, ClassLoader> calcImports(MavenProject project, ClassLoader parent, List<String> imports) {
Map<String, ClassLoader> foreignImports = new HashMap<>();
ClassLoader projectRealm = project.getClassRealm();
if (projectRealm != null) {
foreignImports.put("", projectRealm);
} else {
foreignImports.put("", classRealmManager.getMavenApiRealm());
}
if (parent != null && imports != null) {
for (String parentImport : imports) {
foreignImports.put(parentImport, parent);
}
}
return foreignImports;
}
public <T> T getConfiguredMojo(Class<T> mojoInterface, MavenSession session, MojoExecution mojoExecution)
throws PluginConfigurationException, PluginContainerException {
MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
PluginDescriptor pluginDescriptor = mojoDescriptor.getPluginDescriptor();
ClassRealm pluginRealm = pluginDescriptor.getClassRealm();
if (logger.isDebugEnabled()) {
logger.debug("Configuring mojo " + mojoDescriptor.getId() + " from plugin realm " + pluginRealm);
}
// We are forcing the use of the plugin realm for all lookups that might occur during
// the lifecycle that is part of the lookup. Here we are specifically trying to keep
// lookups that occur in contextualize calls in line with the right realm.
ClassRealm oldLookupRealm = container.setLookupRealm(pluginRealm);
ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(pluginRealm);
try {
T mojo;
try {
mojo = container.lookup(mojoInterface, mojoDescriptor.getRoleHint());
} catch (ComponentLookupException e) {
Throwable cause = e.getCause();
while (cause != null && !(cause instanceof LinkageError)
&& !(cause instanceof ClassNotFoundException)) {
cause = cause.getCause();
}
if ((cause instanceof NoClassDefFoundError) || (cause instanceof ClassNotFoundException)) {
ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
PrintStream ps = new PrintStream(os);
ps.println("Unable to load the mojo '" + mojoDescriptor.getGoal() + "' in the plugin '"
+ pluginDescriptor.getId() + "'. A required class is missing: "
+ cause.getMessage());
pluginRealm.display(ps);
throw new PluginContainerException(mojoDescriptor, pluginRealm, os.toString(), cause);
} else if (cause instanceof LinkageError) {
ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
PrintStream ps = new PrintStream(os);
ps.println("Unable to load the mojo '" + mojoDescriptor.getGoal() + "' in the plugin '"
+ pluginDescriptor.getId() + "' due to an API incompatibility: "
+ e.getClass().getName() + ": " + cause.getMessage());
pluginRealm.display(ps);
throw new PluginContainerException(mojoDescriptor, pluginRealm, os.toString(), cause);
}
throw new PluginContainerException(mojoDescriptor, pluginRealm,
"Unable to load the mojo '" + mojoDescriptor.getGoal()
+ "' (or one of its required components) from the plugin '"
+ pluginDescriptor.getId() + "'",
e);
}
if (mojo instanceof ContextEnabled) {
MavenProject project = session.getCurrentProject();
Map<String, Object> pluginContext = session.getPluginContext(pluginDescriptor, project);
if (pluginContext != null) {
pluginContext.put("project", project);
pluginContext.put("pluginDescriptor", pluginDescriptor);
((ContextEnabled) mojo).setPluginContext(pluginContext);
}
}
if (mojo instanceof Mojo) {
Logger mojoLogger = loggerManager.getLoggerForComponent(mojoDescriptor.getImplementation());
((Mojo) mojo).setLog(new DefaultLog(mojoLogger));
}
Xpp3Dom dom = mojoExecution.getConfiguration();
PlexusConfiguration pomConfiguration;
if (dom == null) {
pomConfiguration = new XmlPlexusConfiguration("configuration");
} else {
pomConfiguration = new XmlPlexusConfiguration(dom);
}
ExpressionEvaluator expressionEvaluator = new PluginParameterExpressionEvaluator(session, mojoExecution);
populatePluginFields(mojo, mojoDescriptor, pluginRealm, pomConfiguration, expressionEvaluator);
return mojo;
} finally {
Thread.currentThread().setContextClassLoader(oldClassLoader);
container.setLookupRealm(oldLookupRealm);
}
}
private void populatePluginFields(Object mojo, MojoDescriptor mojoDescriptor, ClassRealm pluginRealm,
PlexusConfiguration configuration, ExpressionEvaluator expressionEvaluator)
throws PluginConfigurationException {
ComponentConfigurator configurator = null;
String configuratorId = mojoDescriptor.getComponentConfigurator();
if (StringUtils.isEmpty(configuratorId)) {
configuratorId = "basic";
}
try {
// TODO could the configuration be passed to lookup and the configurator known to plexus via the descriptor
// so that this method could entirely be handled by a plexus lookup?
configurator = container.lookup(ComponentConfigurator.class, configuratorId);
ConfigurationListener listener = new DebugConfigurationListener(logger);
ValidatingConfigurationListener validator = new ValidatingConfigurationListener(mojo, mojoDescriptor, listener);
logger.debug(
"Configuring mojo '" + mojoDescriptor.getId() + "' with " + configuratorId + " configurator -->");
configurator.configureComponent(mojo, configuration, expressionEvaluator, pluginRealm, validator);
logger.debug("-- end configuration --");
Collection<Parameter> missingParameters = validator.getMissingParameters();
if (!missingParameters.isEmpty()) {
if ("basic".equals(configuratorId)) {
throw new PluginParameterException(mojoDescriptor, new ArrayList<>(missingParameters));
} else {
/*
* NOTE: Other configurators like the map-oriented one don't call into the listener, so do it the
* hard way.
*/
validateParameters(mojoDescriptor, configuration, expressionEvaluator);
}
}
} catch (ComponentConfigurationException e) {
String message = "Unable to parse configuration of mojo " + mojoDescriptor.getId();
if (e.getFailedConfiguration() != null) {
message += " for parameter " + e.getFailedConfiguration().getName();
}
message += ": " + e.getMessage();
throw new PluginConfigurationException(mojoDescriptor.getPluginDescriptor(), message, e);
} catch (ComponentLookupException e) {
throw new PluginConfigurationException(mojoDescriptor.getPluginDescriptor(),
"Unable to retrieve component configurator " + configuratorId
+ " for configuration of mojo " + mojoDescriptor.getId(),
e);
} catch (NoClassDefFoundError e) {
ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
PrintStream ps = new PrintStream(os);
ps.println("A required class was missing during configuration of mojo " + mojoDescriptor.getId() + ": "
+ e.getMessage());
pluginRealm.display(ps);
throw new PluginConfigurationException(mojoDescriptor.getPluginDescriptor(), os.toString(), e);
} catch (LinkageError e) {
ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
PrintStream ps = new PrintStream(os);
ps.println(
"An API incompatibility was encountered during configuration of mojo " + mojoDescriptor.getId() + ": "
+ e.getClass().getName() + ": " + e.getMessage());
pluginRealm.display(ps);
throw new PluginConfigurationException(mojoDescriptor.getPluginDescriptor(), os.toString(), e);
} finally {
if (configurator != null) {
try {
container.release(configurator);
} catch (ComponentLifecycleException e) {
logger.debug("Failed to release mojo configurator - ignoring.");
}
}
}
}
private void validateParameters(MojoDescriptor mojoDescriptor, PlexusConfiguration configuration,
ExpressionEvaluator expressionEvaluator)
throws ComponentConfigurationException, PluginParameterException {
if (mojoDescriptor.getParameters() == null) {
return;
}
List<Parameter> invalidParameters = new ArrayList<>();
for (Parameter parameter : mojoDescriptor.getParameters()) {
if (!parameter.isRequired()) {
continue;
}
Object value = null;
PlexusConfiguration config = configuration.getChild(parameter.getName(), false);
if (config != null) {
String expression = config.getValue(null);
try {
value = expressionEvaluator.evaluate(expression);
if (value == null) {
value = config.getAttribute("default-value", null);
}
} catch (ExpressionEvaluationException e) {
String msg = "Error evaluating the expression '" + expression + "' for configuration value '"
+ configuration.getName() + "'";
throw new ComponentConfigurationException(configuration, msg, e);
}
}
if (value == null && (config == null || config.getChildCount() <= 0)) {
invalidParameters.add(parameter);
}
}
if (!invalidParameters.isEmpty()) {
throw new PluginParameterException(mojoDescriptor, invalidParameters);
}
}
public void releaseMojo(Object mojo, MojoExecution mojoExecution) {
if (mojo != null) {
try {
container.release(mojo);
} catch (ComponentLifecycleException e) {
String goalExecId = mojoExecution.getGoal();
if (mojoExecution.getExecutionId() != null) {
goalExecId += " {execution: " + mojoExecution.getExecutionId() + "}";
}
logger.debug("Error releasing mojo for " + goalExecId, e);
}
}
}
public ExtensionRealmCache.CacheRecord setupExtensionsRealm(MavenProject project, Plugin plugin,
RepositorySystemSession session)
throws PluginManagerException {
@SuppressWarnings("unchecked")
Map<String, ExtensionRealmCache.CacheRecord> pluginRealms = (Map<String, ExtensionRealmCache.CacheRecord>) project
.getContextValue(KEY_EXTENSIONS_REALMS);
if (pluginRealms == null) {
pluginRealms = new HashMap<>();
project.setContextValue(KEY_EXTENSIONS_REALMS, pluginRealms);
}
final String pluginKey = plugin.getId();
ExtensionRealmCache.CacheRecord extensionRecord = pluginRealms.get(pluginKey);
if (extensionRecord != null) {
return extensionRecord;
}
final List<RemoteRepository> repositories = project.getRemotePluginRepositories();
// resolve plugin version as necessary
if (plugin.getVersion() == null) {
PluginVersionRequest versionRequest = new DefaultPluginVersionRequest(plugin, session, repositories);
try {
plugin.setVersion(pluginVersionResolver.resolve(versionRequest).getVersion());
} catch (PluginVersionResolutionException e) {
throw new PluginManagerException(plugin, e.getMessage(), e);
}
}
// resolve plugin artifacts
List<Artifact> artifacts;
PluginArtifactsCache.Key cacheKey = pluginArtifactsCache.createKey(plugin, null, repositories, session);
PluginArtifactsCache.CacheRecord recordArtifacts;
try {
recordArtifacts = pluginArtifactsCache.get(cacheKey);
} catch (PluginResolutionException e) {
throw new PluginManagerException(plugin, e.getMessage(), e);
}
if (recordArtifacts != null) {
artifacts = recordArtifacts.getArtifacts();
} else {
try {
artifacts = resolveExtensionArtifacts(plugin, repositories, session);
recordArtifacts = pluginArtifactsCache.put(cacheKey, artifacts);
} catch (PluginResolutionException e) {
pluginArtifactsCache.put(cacheKey, e);
pluginArtifactsCache.register(project, cacheKey, recordArtifacts);
throw new PluginManagerException(plugin, e.getMessage(), e);
}
}
pluginArtifactsCache.register(project, cacheKey, recordArtifacts);
// create and cache extensions realms
final ExtensionRealmCache.Key extensionKey = extensionRealmCache.createKey(artifacts);
extensionRecord = extensionRealmCache.get(extensionKey);
if (extensionRecord == null) {
ClassRealm extensionRealm = classRealmManager.createExtensionRealm(plugin, toAetherArtifacts(artifacts));
// TODO figure out how to use the same PluginDescriptor when running mojos
PluginDescriptor pluginDescriptor = null;
if (plugin.isExtensions() && !artifacts.isEmpty()) {
// ignore plugin descriptor parsing errors at this point
// these errors will reported during calculation of project build execution plan
try {
pluginDescriptor = extractPluginDescriptor(artifacts.get(0), plugin);
} catch (PluginDescriptorParsingException | InvalidPluginDescriptorException e) {
// ignore, see above
}
}
discoverPluginComponents(extensionRealm, plugin, pluginDescriptor);
ExtensionDescriptor extensionDescriptor = null;
Artifact extensionArtifact = artifacts.get(0);
try {
extensionDescriptor = extensionDescriptorBuilder.build(extensionArtifact.getFile());
} catch (IOException e) {
String message = "Invalid extension descriptor for " + plugin.getId() + ": " + e.getMessage();
if (logger.isDebugEnabled()) {
logger.error(message, e);
} else {
logger.error(message);
}
}
extensionRecord = extensionRealmCache.put(extensionKey, extensionRealm, extensionDescriptor, artifacts);
}
extensionRealmCache.register(project, extensionKey, extensionRecord);
pluginRealms.put(pluginKey, extensionRecord);
return extensionRecord;
}
private List<Artifact> resolveExtensionArtifacts(Plugin extensionPlugin, List<RemoteRepository> repositories,
RepositorySystemSession session)
throws PluginResolutionException {
DependencyNode root = pluginDependenciesResolver.resolve(extensionPlugin, null, null, repositories, session);
PreorderNodeListGenerator nlg = new PreorderNodeListGenerator();
root.accept(nlg);
return toMavenArtifacts(root, nlg);
}
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright 2021 the original author or authors.
*
* 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 org.mvndaemon.mvnd.plugin;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.apache.maven.plugin.descriptor.Parameter;
import org.codehaus.plexus.component.configurator.ConfigurationListener;
/**
* A configuration listener to help validate the plugin configuration. For instance, check for required but missing
* parameters.
*
* @author Benjamin Bentmann
*/
class ValidatingConfigurationListener
implements ConfigurationListener {
private final Object mojo;
private final ConfigurationListener delegate;
private final Map<String, Parameter> missingParameters;
ValidatingConfigurationListener(Object mojo, MojoDescriptor mojoDescriptor, ConfigurationListener delegate) {
this.mojo = mojo;
this.delegate = delegate;
this.missingParameters = new HashMap<>();
if (mojoDescriptor.getParameters() != null) {
for (Parameter param : mojoDescriptor.getParameters()) {
if (param.isRequired()) {
missingParameters.put(param.getName(), param);
}
}
}
}
public Collection<Parameter> getMissingParameters() {
return missingParameters.values();
}
public void notifyFieldChangeUsingSetter(String fieldName, Object value, Object target) {
delegate.notifyFieldChangeUsingSetter(fieldName, value, target);
if (mojo == target) {
notify(fieldName, value);
}
}
public void notifyFieldChangeUsingReflection(String fieldName, Object value, Object target) {
delegate.notifyFieldChangeUsingReflection(fieldName, value, target);
if (mojo == target) {
notify(fieldName, value);
}
}
private void notify(String fieldName, Object value) {
if (value != null) {
missingParameters.remove(fieldName);
}
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2019 the original author or authors.
*
* 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 org.mvndaemon.mvnd.it;
import java.io.IOException;
import java.nio.file.Path;
import javax.inject.Inject;
import org.junit.jupiter.api.Test;
import org.mvndaemon.mvnd.assertj.TestClientOutput;
import org.mvndaemon.mvnd.client.Client;
import org.mvndaemon.mvnd.client.DaemonParameters;
import org.mvndaemon.mvnd.common.Message;
import org.mvndaemon.mvnd.junit.MvndTest;
import org.mvndaemon.mvnd.junit.TestUtils;
import static org.junit.jupiter.api.Assertions.assertEquals;
@MvndTest(projectDir = "src/test/projects/concurrent-downloads")
public class ConcurrentDownloadsTest {
@Inject
Client client;
@Inject
DaemonParameters parameters;
@Test
void build() throws IOException, InterruptedException {
final Path localMavenRepo = parameters.mavenRepoLocal();
TestUtils.deleteDir(localMavenRepo);
final TestClientOutput o = new TestClientOutput();
client.execute(o, "clean", "install", "-e", "-B").assertSuccess();
int maxCur = 0;
int cur = 0;
for (Message m : o.getMessages()) {
if (m instanceof Message.TransferEvent) {
Message.TransferEvent event = (Message.TransferEvent) m;
String resource = event.getResourceName();
if (resource.contains("apache-camel") || resource.contains("apache-activemq")) {
if (m.getType() == Message.TRANSFER_STARTED) {
cur++;
maxCur = Math.max(cur, maxCur);
} else if (m.getType() == Message.TRANSFER_SUCCEEDED) {
cur--;
}
}
}
}
assertEquals(2, maxCur);
}
}

View File

@@ -220,8 +220,8 @@ public class MvndTestExtension implements BeforeAllCallback, BeforeEachCallback,
localMavenRepository, settingsPath,
logback,
TimeUtils.toDuration(Environment.MVND_IDLE_TIMEOUT.getDefault()),
TimeUtils.toDuration(Environment.MVND_KEEP_ALIVE.getDefault()),
Integer.parseInt(Environment.MVND_MAX_LOST_KEEP_ALIVE.getDefault()));
TimeUtils.toDuration(Environment.MVND_KEEP_ALIVE.getDefault()).multipliedBy(10),
Integer.parseInt(Environment.MVND_MAX_LOST_KEEP_ALIVE.getDefault()) * 10);
final TestRegistry registry = new TestRegistry(parameters.registry());
return new MvndResource(parameters, registry, isNative, timeoutMs);

View File

@@ -0,0 +1,3 @@
-Dmaven.wagon.httpconnectionManager.ttlSeconds=120
-Dmaven.wagon.http.retryHandler.requestSentEnabled=true
-Dmaven.wagon.http.retryHandler.count=10

View File

@@ -0,0 +1,54 @@
<!--
Copyright 2019 the original author or authors.
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.
-->
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.mvndaemon.mvnd.test.concurrent-downloads</groupId>
<artifactId>concurrent-downloads</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>concurrent-downloads-mod1</artifactId>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<dependencies>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>apache-activemq</artifactId>
<version>5.16.0</version>
<classifier>bin</classifier>
<type>tar.gz</type>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2019 the original author or authors.
*
* 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 org.mvndaemon.mvnd.test.concurrent.downloads.mod1;
public interface Greeting {
public String greet();
}

View File

@@ -0,0 +1,53 @@
<!--
Copyright 2019 the original author or authors.
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.
-->
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.mvndaemon.mvnd.test.concurrent-downloads</groupId>
<artifactId>concurrent-downloads</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>concurrent-downloads-mod2</artifactId>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<dependencies>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>apache-camel</artifactId>
<version>3.7.0</version>
<type>zip</type>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,24 @@
/*
* Copyright 2019 the original author or authors.
*
* 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 org.mvndaemon.mvnd.test.concurrent.downloads.mod2;
public class Hi {
public String greet() {
return "Hi";
}
}

View File

@@ -0,0 +1,76 @@
<!--
Copyright 2019 the original author or authors.
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.
-->
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.mvndaemon.mvnd.test.concurrent-downloads</groupId>
<artifactId>concurrent-downloads</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
<maven-clean-plugin.version>2.5</maven-clean-plugin.version>
<maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version>
<maven-install-plugin.version>2.4</maven-install-plugin.version>
<maven-resources-plugin.version>2.6</maven-resources-plugin.version>
<maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>
</properties>
<modules>
<module>mod1</module>
<module>mod2</module>
</modules>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>${maven-clean-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<version>${maven-install-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>${maven-resources-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>