You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by cs...@apache.org on 2022/03/13 18:08:26 UTC

[maven] branch MNG-7055-g-level-md-mvn-39x created (now 0ddf3be)

This is an automated email from the ASF dual-hosted git repository.

cstamas pushed a change to branch MNG-7055-g-level-md-mvn-39x
in repository https://gitbox.apache.org/repos/asf/maven.git.


      at 0ddf3be  [MNG-7055] Fix G level metadata handling (#555)

This branch includes the following new commits:

     new 0ddf3be  [MNG-7055] Fix G level metadata handling (#555)

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


[maven] 01/01: [MNG-7055] Fix G level metadata handling (#555)

Posted by cs...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

cstamas pushed a commit to branch MNG-7055-g-level-md-mvn-39x
in repository https://gitbox.apache.org/repos/asf/maven.git

commit 0ddf3be40dd3cb1d9dab4519b9cbf0f9b777c9c0
Author: Tamas Cservenak <ta...@cservenak.net>
AuthorDate: Sun Mar 13 19:07:26 2022 +0100

    [MNG-7055] Fix G level metadata handling (#555)
    
    Maven Artifact Transfer silently prevents group level metadata to reach Resolver and causes metadata loss on install/deploy.
    Fix is to "bridge" this from maven-resolver-provider (and core) by reusing the actual metadata that
    m-plugin-p:addPluginArtifactMetadata mojo adds, but m-a-t filters out.
    
    This is backport of commit d141957ff5a3c08ae53c68e4ba19b49055f717c4 to maven-3.9.x branch.
---
 maven-compat/pom.xml                               |   5 +
 .../artifact/deployer/ArtifactDeployerTest.java    |  36 ++++--
 .../artifact/installer/ArtifactInstallerTest.java  |  28 ++++-
 .../DefaultPluginsMetadataInfoProvider.java        | 137 +++++++++++++++++++++
 .../resources/META-INF/plexus/default-bindings.xml |  28 ++---
 .../maven/repository/internal/PluginsMetadata.java | 117 ++++++++++++++++++
 .../internal/PluginsMetadataGenerator.java         | 130 +++++++++++++++++++
 .../internal/PluginsMetadataGeneratorFactory.java  |  67 ++++++++++
 .../internal/PluginsMetadataInfoProvider.java      |  47 +++++++
 9 files changed, 567 insertions(+), 28 deletions(-)

diff --git a/maven-compat/pom.xml b/maven-compat/pom.xml
index 4f79ac8..ed7bce8 100644
--- a/maven-compat/pom.xml
+++ b/maven-compat/pom.xml
@@ -100,6 +100,11 @@ under the License.
     </dependency>
 
     <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
       <groupId>org.apache.maven.wagon</groupId>
       <artifactId>wagon-file</artifactId>
       <scope>test</scope>
diff --git a/maven-compat/src/test/java/org/apache/maven/artifact/deployer/ArtifactDeployerTest.java b/maven-compat/src/test/java/org/apache/maven/artifact/deployer/ArtifactDeployerTest.java
index f929f48..ed0a43c 100644
--- a/maven-compat/src/test/java/org/apache/maven/artifact/deployer/ArtifactDeployerTest.java
+++ b/maven-compat/src/test/java/org/apache/maven/artifact/deployer/ArtifactDeployerTest.java
@@ -24,8 +24,12 @@ import java.io.File;
 import org.apache.maven.artifact.AbstractArtifactComponentTestCase;
 import org.apache.maven.artifact.Artifact;
 import org.apache.maven.artifact.repository.ArtifactRepository;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.session.scope.internal.SessionScope;
 import org.codehaus.plexus.util.FileUtils;
 
+import static org.mockito.Mockito.mock;
+
 /**
  * @author <a href="mailto:jason@maven.org">Jason van Zyl</a>
  */
@@ -34,12 +38,16 @@ public class ArtifactDeployerTest
 {
     private ArtifactDeployer artifactDeployer;
 
+    private SessionScope sessionScope;
+
     protected void setUp()
         throws Exception
     {
         super.setUp();
 
         artifactDeployer = (ArtifactDeployer) lookup( ArtifactDeployer.ROLE );
+
+        sessionScope = lookup( SessionScope.class );
     }
 
     protected String component()
@@ -50,18 +58,28 @@ public class ArtifactDeployerTest
     public void testArtifactInstallation()
         throws Exception
     {
-        String artifactBasedir = new File( getBasedir(), "src/test/resources/artifact-install" ).getAbsolutePath();
+        sessionScope.enter();
+        try
+        {
+            sessionScope.seed(MavenSession.class, mock(MavenSession.class));
+
+            String artifactBasedir = new File( getBasedir(), "src/test/resources/artifact-install" ).getAbsolutePath();
 
-        Artifact artifact = createArtifact( "artifact", "1.0" );
+            Artifact artifact = createArtifact( "artifact", "1.0" );
 
-        File file = new File( artifactBasedir, "artifact-1.0.jar" );
-        assertEquals( "dummy", FileUtils.fileRead( file, "UTF-8" ).trim() );
+            File file = new File( artifactBasedir, "artifact-1.0.jar" );
+            assertEquals( "dummy", FileUtils.fileRead( file, "UTF-8" ).trim() );
 
-        artifactDeployer.deploy( file, artifact, remoteRepository(), localRepository() );
+            artifactDeployer.deploy( file, artifact, remoteRepository(), localRepository() );
 
-        ArtifactRepository remoteRepository = remoteRepository();
-        File deployedFile = new File( remoteRepository.getBasedir(), remoteRepository.pathOf( artifact ) );
-        assertTrue( deployedFile.exists() );
-        assertEquals( "dummy", FileUtils.fileRead( deployedFile, "UTF-8" ).trim() );
+            ArtifactRepository remoteRepository = remoteRepository();
+            File deployedFile = new File( remoteRepository.getBasedir(), remoteRepository.pathOf( artifact ) );
+            assertTrue( deployedFile.exists() );
+            assertEquals( "dummy", FileUtils.fileRead( deployedFile, "UTF-8" ).trim() );
+        }
+        finally
+        {
+            sessionScope.exit();
+        }
     }
 }
\ No newline at end of file
diff --git a/maven-compat/src/test/java/org/apache/maven/artifact/installer/ArtifactInstallerTest.java b/maven-compat/src/test/java/org/apache/maven/artifact/installer/ArtifactInstallerTest.java
index 21bfb2b..66ba9e9 100644
--- a/maven-compat/src/test/java/org/apache/maven/artifact/installer/ArtifactInstallerTest.java
+++ b/maven-compat/src/test/java/org/apache/maven/artifact/installer/ArtifactInstallerTest.java
@@ -23,6 +23,10 @@ import java.io.File;
 
 import org.apache.maven.artifact.AbstractArtifactComponentTestCase;
 import org.apache.maven.artifact.Artifact;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.session.scope.internal.SessionScope;
+
+import static org.mockito.Mockito.mock;
 
 /**
  * @author <a href="mailto:jason@maven.org">Jason van Zyl</a>
@@ -32,12 +36,16 @@ public class ArtifactInstallerTest
 {
     private ArtifactInstaller artifactInstaller;
 
+    private SessionScope sessionScope;
+
     protected void setUp()
         throws Exception
     {
         super.setUp();
 
         artifactInstaller = (ArtifactInstaller) lookup( ArtifactInstaller.ROLE );
+
+        sessionScope = lookup( SessionScope.class );
     }
 
     protected String component()
@@ -48,14 +56,24 @@ public class ArtifactInstallerTest
     public void testArtifactInstallation()
         throws Exception
     {
-        String artifactBasedir = new File( getBasedir(), "src/test/resources/artifact-install" ).getAbsolutePath();
+        sessionScope.enter();
+        try
+        {
+            sessionScope.seed(MavenSession.class, mock(MavenSession.class));
+
+            String artifactBasedir = new File( getBasedir(), "src/test/resources/artifact-install" ).getAbsolutePath();
 
-        Artifact artifact = createArtifact( "artifact", "1.0" );
+            Artifact artifact = createArtifact( "artifact", "1.0" );
 
-        File source = new File( artifactBasedir, "artifact-1.0.jar" );
+            File source = new File( artifactBasedir, "artifact-1.0.jar" );
 
-        artifactInstaller.install( source, artifact, localRepository() );
+            artifactInstaller.install( source, artifact, localRepository() );
 
-        assertLocalArtifactPresent( artifact );
+            assertLocalArtifactPresent( artifact );
+        }
+        finally
+        {
+            sessionScope.exit();
+        }
     }
 }
\ No newline at end of file
diff --git a/maven-core/src/main/java/org/apache/maven/execution/infoproviders/DefaultPluginsMetadataInfoProvider.java b/maven-core/src/main/java/org/apache/maven/execution/infoproviders/DefaultPluginsMetadataInfoProvider.java
new file mode 100644
index 0000000..616a3a8
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/execution/infoproviders/DefaultPluginsMetadataInfoProvider.java
@@ -0,0 +1,137 @@
+package org.apache.maven.execution.infoproviders;
+
+/*
+ * 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.Objects;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Provider;
+import javax.inject.Singleton;
+
+import org.apache.maven.artifact.repository.metadata.Metadata;
+import org.apache.maven.artifact.repository.metadata.Plugin;
+import org.apache.maven.artifact.repository.metadata.RepositoryMetadata;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.repository.internal.PluginsMetadataInfoProvider;
+import org.apache.maven.repository.legacy.metadata.ArtifactMetadata;
+import org.eclipse.aether.artifact.Artifact;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Default implementation of {@link PluginsMetadataInfoProvider}.
+ */
+@Named
+@Singleton
+public class DefaultPluginsMetadataInfoProvider
+    implements PluginsMetadataInfoProvider
+{
+    private final Provider<MavenSession> mavenSessionProvider;
+
+    @Inject
+    public DefaultPluginsMetadataInfoProvider( final Provider<MavenSession> mavenSessionProvider )
+    {
+        this.mavenSessionProvider = requireNonNull( mavenSessionProvider );
+    }
+
+    @Override
+    public PluginInfo getPluginInfo( final Artifact artifact )
+    {
+        MavenSession mavenSession = mavenSessionProvider.get();
+        if ( mavenSession != null )
+        {
+            MavenProject mavenProject = searchForProject( mavenSession, artifact );
+            if ( mavenProject != null && "maven-plugin".equals( mavenProject.getPackaging() ) )
+            {
+                Plugin plugin = searchForPluginGroupLevelRepositoryMetadata( mavenProject );
+
+                if ( plugin != null )
+                {
+                    return new PluginInfo()
+                    {
+                        @Override
+                        public String getPluginGroupId()
+                        {
+                            return artifact.getGroupId();
+                        }
+
+                        @Override
+                        public String getPluginArtifactId()
+                        {
+                            return artifact.getArtifactId();
+                        }
+
+                        @Override
+                        public String getPluginPrefix()
+                        {
+                            return plugin.getPrefix();
+                        }
+
+                        @Override
+                        public String getPluginName()
+                        {
+                            return plugin.getName();
+                        }
+                    };
+                }
+            }
+        }
+
+        return null;
+    }
+
+    private MavenProject searchForProject( MavenSession mavenSession, Artifact artifact )
+    {
+        for ( MavenProject mavenProject : mavenSession.getProjects() )
+        {
+            if ( mavenProject.getArtifact() != null
+                && Objects.equals( mavenProject.getGroupId(), artifact.getGroupId() )
+                && Objects.equals( mavenProject.getArtifactId(), artifact.getArtifactId() ) )
+            {
+                return mavenProject;
+            }
+        }
+        return null;
+    }
+
+    private Plugin searchForPluginGroupLevelRepositoryMetadata( MavenProject mavenProject )
+    {
+        org.apache.maven.artifact.Artifact projectArtifact = mavenProject.getArtifact();
+        for ( ArtifactMetadata artifactMetadata : projectArtifact.getMetadataList() )
+        {
+            if ( artifactMetadata instanceof RepositoryMetadata )
+            {
+                RepositoryMetadata repositoryMetadata = (RepositoryMetadata) artifactMetadata;
+                Metadata metadata = repositoryMetadata.getMetadata();
+
+                for ( Plugin plugin : metadata.getPlugins() )
+                {
+                    if ( Objects.equals( plugin.getArtifactId(), mavenProject.getArtifactId() ) )
+                    {
+                        return plugin;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+}
diff --git a/maven-core/src/main/resources/META-INF/plexus/default-bindings.xml b/maven-core/src/main/resources/META-INF/plexus/default-bindings.xml
index 4a303db..e35f931 100644
--- a/maven-core/src/main/resources/META-INF/plexus/default-bindings.xml
+++ b/maven-core/src/main/resources/META-INF/plexus/default-bindings.xml
@@ -41,10 +41,10 @@ Mappings to default lifecycle, specific for each packaging.
             <!-- START SNIPPET: pom-lifecycle -->
             <phases>
               <install>
-                org.apache.maven.plugins:maven-install-plugin:2.4:install
+                org.apache.maven.plugins:maven-install-plugin:3.0.0-M1:install
               </install>
               <deploy>
-                org.apache.maven.plugins:maven-deploy-plugin:2.7:deploy
+                org.apache.maven.plugins:maven-deploy-plugin:3.0.0-M2:deploy
               </deploy>
             </phases>
             <!-- END SNIPPET: pom-lifecycle -->
@@ -85,10 +85,10 @@ Mappings to default lifecycle, specific for each packaging.
                 org.apache.maven.plugins:maven-jar-plugin:2.4:jar
               </package>
               <install>
-                org.apache.maven.plugins:maven-install-plugin:2.4:install
+                org.apache.maven.plugins:maven-install-plugin:3.0.0-M1:install
               </install>
               <deploy>
-                org.apache.maven.plugins:maven-deploy-plugin:2.7:deploy
+                org.apache.maven.plugins:maven-deploy-plugin:3.0.0-M2:deploy
               </deploy>
             </phases>
             <!-- END SNIPPET: jar-lifecycle -->
@@ -129,10 +129,10 @@ Mappings to default lifecycle, specific for each packaging.
                 org.apache.maven.plugins:maven-ejb-plugin:2.3:ejb
               </package>
               <install>
-                org.apache.maven.plugins:maven-install-plugin:2.4:install
+                org.apache.maven.plugins:maven-install-plugin:3.0.0-M1:install
               </install>
               <deploy>
-                org.apache.maven.plugins:maven-deploy-plugin:2.7:deploy
+                org.apache.maven.plugins:maven-deploy-plugin:3.0.0-M2:deploy
               </deploy>
             </phases>
             <!-- END SNIPPET: ejb-lifecycle -->
@@ -177,10 +177,10 @@ Mappings to default lifecycle, specific for each packaging.
                 org.apache.maven.plugins:maven-plugin-plugin:3.2:addPluginArtifactMetadata
               </package>
               <install>
-                org.apache.maven.plugins:maven-install-plugin:2.4:install
+                org.apache.maven.plugins:maven-install-plugin:3.0.0-M1:install
               </install>
               <deploy>
-                org.apache.maven.plugins:maven-deploy-plugin:2.7:deploy
+                org.apache.maven.plugins:maven-deploy-plugin:3.0.0-M2:deploy
               </deploy>
             </phases>
             <!-- END SNIPPET: maven-plugin-lifecycle -->
@@ -221,10 +221,10 @@ Mappings to default lifecycle, specific for each packaging.
                 org.apache.maven.plugins:maven-war-plugin:2.2:war
               </package>
               <install>
-                org.apache.maven.plugins:maven-install-plugin:2.4:install
+                org.apache.maven.plugins:maven-install-plugin:3.0.0-M1:install
               </install>
               <deploy>
-                org.apache.maven.plugins:maven-deploy-plugin:2.7:deploy
+                org.apache.maven.plugins:maven-deploy-plugin:3.0.0-M2:deploy
               </deploy>
             </phases>
             <!-- END SNIPPET: war-lifecycle -->
@@ -256,10 +256,10 @@ Mappings to default lifecycle, specific for each packaging.
                 org.apache.maven.plugins:maven-ear-plugin:2.8:ear
               </package>
               <install>
-                org.apache.maven.plugins:maven-install-plugin:2.4:install
+                org.apache.maven.plugins:maven-install-plugin:3.0.0-M1:install
               </install>
               <deploy>
-                org.apache.maven.plugins:maven-deploy-plugin:2.7:deploy
+                org.apache.maven.plugins:maven-deploy-plugin:3.0.0-M2:deploy
               </deploy>
             </phases>
             <!-- END SNIPPET: ear-lifecycle -->
@@ -300,10 +300,10 @@ Mappings to default lifecycle, specific for each packaging.
                 org.apache.maven.plugins:maven-rar-plugin:2.2:rar
               </package>
               <install>
-                org.apache.maven.plugins:maven-install-plugin:2.4:install
+                org.apache.maven.plugins:maven-install-plugin:3.0.0-M1:install
               </install>
               <deploy>
-                org.apache.maven.plugins:maven-deploy-plugin:2.7:deploy
+                org.apache.maven.plugins:maven-deploy-plugin:3.0.0-M2:deploy
               </deploy>
             </phases>
             <!-- END SNIPPET: rar-lifecycle -->
diff --git a/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/PluginsMetadata.java b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/PluginsMetadata.java
new file mode 100644
index 0000000..7a6c45a
--- /dev/null
+++ b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/PluginsMetadata.java
@@ -0,0 +1,117 @@
+package org.apache.maven.repository.internal;
+
+/*
+ * 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.File;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+import org.apache.maven.artifact.repository.metadata.Metadata;
+import org.apache.maven.artifact.repository.metadata.Plugin;
+import org.apache.maven.repository.internal.PluginsMetadataInfoProvider.PluginInfo;
+import org.eclipse.aether.artifact.Artifact;
+
+/**
+ * Plugin G level metadata.
+ */
+final class PluginsMetadata
+    extends MavenMetadata
+{
+    private final PluginInfo pluginInfo;
+
+    PluginsMetadata( PluginInfo pluginInfo, Date timestamp )
+    {
+        super( createRepositoryMetadata( pluginInfo ), null, timestamp );
+        this.pluginInfo = pluginInfo;
+    }
+
+    PluginsMetadata( PluginInfo pluginInfo, File file, Date timestamp )
+    {
+        super( createRepositoryMetadata( pluginInfo ), file, timestamp );
+        this.pluginInfo = pluginInfo;
+    }
+
+    private static Metadata createRepositoryMetadata( PluginInfo pluginInfo )
+    {
+        Metadata result = new Metadata();
+        Plugin plugin = new Plugin();
+        plugin.setPrefix( pluginInfo.getPluginPrefix() );
+        plugin.setArtifactId( pluginInfo.getPluginArtifactId() );
+        plugin.setName( pluginInfo.getPluginName() );
+        result.getPlugins().add( plugin );
+        return result;
+    }
+
+    @Override
+    protected void merge( Metadata recessive )
+    {
+        List<Plugin> recessivePlugins = recessive.getPlugins();
+        List<Plugin> plugins = metadata.getPlugins();
+        if ( !plugins.isEmpty() )
+        {
+            LinkedHashMap<String, Plugin> mergedPlugins = new LinkedHashMap<>();
+            recessivePlugins.forEach( p -> mergedPlugins.put( p.getPrefix(), p ) );
+            plugins.forEach( p -> mergedPlugins.put( p.getPrefix(), p ) );
+            metadata.setPlugins( new ArrayList<>( mergedPlugins.values() ) );
+        }
+    }
+
+    public Object getKey()
+    {
+        return getGroupId();
+    }
+
+    public static Object getKey( Artifact artifact )
+    {
+        return artifact.getGroupId();
+    }
+
+    @Override
+    public MavenMetadata setFile( File file )
+    {
+        return new PluginsMetadata( pluginInfo, file, timestamp );
+    }
+
+    @Override
+    public String getGroupId()
+    {
+        return pluginInfo.getPluginGroupId();
+    }
+
+    @Override
+    public String getArtifactId()
+    {
+        return "";
+    }
+
+    @Override
+    public String getVersion()
+    {
+        return "";
+    }
+
+    @Override
+    public Nature getNature()
+    {
+        return Nature.RELEASE_OR_SNAPSHOT;
+    }
+}
diff --git a/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/PluginsMetadataGenerator.java b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/PluginsMetadataGenerator.java
new file mode 100644
index 0000000..4f35568
--- /dev/null
+++ b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/PluginsMetadataGenerator.java
@@ -0,0 +1,130 @@
+package org.apache.maven.repository.internal;
+
+/*
+ * 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.Collections;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.maven.repository.internal.PluginsMetadataInfoProvider.PluginInfo;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.deployment.DeployRequest;
+import org.eclipse.aether.impl.MetadataGenerator;
+import org.eclipse.aether.installation.InstallRequest;
+import org.eclipse.aether.metadata.Metadata;
+import org.eclipse.aether.util.ConfigUtils;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Plugin G level metadata.
+ */
+class PluginsMetadataGenerator
+    implements MetadataGenerator
+{
+    private final PluginsMetadataInfoProvider pluginsMetadataInfoProvider;
+
+    private final Map<Object, PluginsMetadata> plugins;
+
+    private final Map<Object, PluginsMetadata> processedPlugins;
+
+    private final Date timestamp;
+
+    PluginsMetadataGenerator( PluginsMetadataInfoProvider pluginsMetadataInfoProvider,
+                              RepositorySystemSession session,
+                              InstallRequest request )
+    {
+        this( pluginsMetadataInfoProvider, session, request.getMetadata() );
+    }
+
+    PluginsMetadataGenerator( PluginsMetadataInfoProvider pluginsMetadataInfoProvider,
+                              RepositorySystemSession session,
+                              DeployRequest request )
+    {
+        this( pluginsMetadataInfoProvider, session, request.getMetadata() );
+    }
+
+    private PluginsMetadataGenerator( PluginsMetadataInfoProvider pluginsMetadataInfoProvider,
+                                      RepositorySystemSession session,
+                                      Collection<? extends Metadata> metadatas )
+    {
+        this.pluginsMetadataInfoProvider = requireNonNull( pluginsMetadataInfoProvider );
+        this.plugins = new LinkedHashMap<>();
+        this.processedPlugins = new LinkedHashMap<>();
+        this.timestamp = (Date) ConfigUtils.getObject( session, new Date(), "maven.startTime" );
+
+        /*
+         * NOTE: This should be considered a quirk to support interop with Maven's legacy ArtifactDeployer which
+         * processes one artifact at a time and hence cannot associate the artifacts from the same project to use the
+         * same version index. Allowing the caller to pass in metadata from a previous deployment allows to re-establish
+         * the association between the artifacts of the same project.
+         */
+        for ( Iterator<? extends Metadata> it = metadatas.iterator(); it.hasNext(); )
+        {
+            Metadata metadata = it.next();
+            if ( metadata instanceof PluginsMetadata )
+            {
+                it.remove();
+                PluginsMetadata pluginMetadata = ( PluginsMetadata ) metadata;
+                processedPlugins.put( pluginMetadata.getKey(), pluginMetadata );
+            }
+        }
+    }
+
+    @Override
+    public Collection<? extends Metadata> prepare( Collection<? extends Artifact> artifacts )
+    {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public Artifact transformArtifact( Artifact artifact )
+    {
+        return artifact;
+    }
+
+    @Override
+    public Collection<? extends Metadata> finish( Collection<? extends Artifact> artifacts )
+    {
+        for ( Artifact artifact : artifacts )
+        {
+            PluginInfo pluginInfo = pluginsMetadataInfoProvider.getPluginInfo( artifact );
+            if ( pluginInfo != null )
+            {
+                Object key = PluginsMetadata.getKey( artifact );
+                if ( processedPlugins.get( key ) == null )
+                {
+                    PluginsMetadata pluginMetadata = plugins.get( key );
+                    if ( pluginMetadata == null )
+                    {
+                        pluginMetadata = new PluginsMetadata( pluginInfo, timestamp );
+                        plugins.put( key, pluginMetadata );
+                    }
+                }
+            }
+        }
+
+        return plugins.values();
+    }
+}
diff --git a/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/PluginsMetadataGeneratorFactory.java b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/PluginsMetadataGeneratorFactory.java
new file mode 100644
index 0000000..19d499b
--- /dev/null
+++ b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/PluginsMetadataGeneratorFactory.java
@@ -0,0 +1,67 @@
+package org.apache.maven.repository.internal;
+
+/*
+ * 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 javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.deployment.DeployRequest;
+import org.eclipse.aether.impl.MetadataGenerator;
+import org.eclipse.aether.impl.MetadataGeneratorFactory;
+import org.eclipse.aether.installation.InstallRequest;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Plugin G level metadata.
+ */
+@Named( "plugins" )
+@Singleton
+public class PluginsMetadataGeneratorFactory
+    implements MetadataGeneratorFactory
+{
+    private final PluginsMetadataInfoProvider pluginsMetadataInfoProvider;
+
+    @Inject
+    public PluginsMetadataGeneratorFactory( PluginsMetadataInfoProvider pluginsMetadataInfoProvider )
+    {
+        this.pluginsMetadataInfoProvider = requireNonNull( pluginsMetadataInfoProvider );
+    }
+
+    @Override
+    public MetadataGenerator newInstance( RepositorySystemSession session, InstallRequest request )
+    {
+        return new PluginsMetadataGenerator( pluginsMetadataInfoProvider, session, request );
+    }
+
+    @Override
+    public MetadataGenerator newInstance( RepositorySystemSession session, DeployRequest request )
+    {
+        return new PluginsMetadataGenerator( pluginsMetadataInfoProvider, session, request );
+    }
+
+    @Override
+    public float getPriority()
+    {
+        return 5;
+    }
+}
diff --git a/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/PluginsMetadataInfoProvider.java b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/PluginsMetadataInfoProvider.java
new file mode 100644
index 0000000..806fa46
--- /dev/null
+++ b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/PluginsMetadataInfoProvider.java
@@ -0,0 +1,47 @@
+package org.apache.maven.repository.internal;
+
+/*
+ * 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 org.eclipse.aether.artifact.Artifact;
+
+/**
+ * Plugin G level metadata provider.
+ */
+public interface PluginsMetadataInfoProvider
+{
+    /**
+     * The required data for G level metadata.
+     */
+    interface PluginInfo
+    {
+        String getPluginGroupId();
+
+        String getPluginArtifactId();
+
+        String getPluginPrefix();
+
+        String getPluginName();
+    }
+
+    /**
+     * Returns {@link PluginInfo} corresponding for passed in {@link Artifact}, or {@code null}.
+     */
+    PluginInfo getPluginInfo( Artifact artifact );
+}