1515 */
1616package io .github .ascopes .protobufmavenplugin .dependencies .aether ;
1717
18- import static java .util .Objects . requireNonNull ;
18+ import static java .util .function . Predicate . not ;
1919
2020import io .github .ascopes .protobufmavenplugin .utils .ResolutionException ;
2121import io .github .ascopes .protobufmavenplugin .utils .StringUtils ;
22- import java .util .ArrayList ;
2322import java .util .Collection ;
24- import java .util .Collections ;
2523import java .util .List ;
24+ import java .util .Objects ;
2625import java .util .Set ;
26+ import java .util .stream .Collectors ;
2727import javax .inject .Inject ;
2828import javax .inject .Named ;
29- import org .apache .maven .execution .MavenSession ;
3029import org .apache .maven .execution .scope .MojoExecutionScoped ;
30+ import org .apache .maven .project .MavenProject ;
3131import org .eclipse .aether .RepositorySystem ;
32+ import org .eclipse .aether .RepositorySystemSession ;
3233import org .eclipse .aether .artifact .Artifact ;
3334import org .eclipse .aether .collection .CollectRequest ;
3435import org .eclipse .aether .graph .Dependency ;
3940import org .eclipse .aether .resolution .DependencyRequest ;
4041import org .eclipse .aether .resolution .DependencyResolutionException ;
4142import org .eclipse .aether .resolution .DependencyResult ;
42- import org .eclipse .aether .util .filter .ScopeDependencyFilter ;
4343import org .eclipse .sisu .Description ;
4444import org .slf4j .Logger ;
4545import org .slf4j .LoggerFactory ;
5353 *
5454 * <p>Warning: the code in this class is very fragile and changing it can easily result in the
5555 * introduction of regressions for users. If you need to alter it, be very sure that you know what
56- * you are doing!
56+ * you are doing and that <strong>all</strong> possible error cases are handled in a remotely
57+ * sensible way to avoid bug reports due to ambiguous handling!
5758 *
5859 * @author Ashley Scopes
5960 * @since 2.4.4
@@ -66,79 +67,61 @@ final class AetherResolver {
6667 private static final Logger log = LoggerFactory .getLogger (AetherResolver .class );
6768
6869 private final RepositorySystem repositorySystem ;
69- private final ProtobufMavenPluginRepositorySession repositorySystemSession ;
70- private final List < RemoteRepository > remoteRepositories ;
70+ private final RepositorySystemSession repositorySystemSession ;
71+ private final MavenProject mavenProject ;
7172
7273 @ Inject
7374 AetherResolver (
7475 RepositorySystem repositorySystem ,
75- MavenSession mavenSession ,
76- ProtobufMavenPluginRepositorySession repositorySystemSession
76+ RepositorySystemSession repositorySystemSession ,
77+ MavenProject mavenProject
7778 ) {
7879 this .repositorySystem = repositorySystem ;
7980 this .repositorySystemSession = repositorySystemSession ;
80-
81- // Prior to v2.12.0, we used the ProjectBuildingRequest on the MavenSession
82- // and used RepositoryUtils.toRepos to create the repository list. GH-579
83- // was raised to report that the <repositories> block in the POM was being
84- // ignored. This appears to be due to the project building request only
85- // looking at what is in ~/.m2/settings.xml. The current project remote
86- // repositories seems to be what we need to use instead here.
87- remoteRepositories = mavenSession .getCurrentProject ().getRemoteProjectRepositories ();
81+ this .mavenProject = mavenProject ;
8882 }
8983
90- Artifact resolveRequiredArtifact (Artifact artifact ) throws ResolutionException {
91- log .info ("Resolving artifact \" {}\" " , artifact );
84+ Artifact resolveArtifact (Artifact artifact ) throws ResolutionException {
85+ log .debug ("Resolving artifact \" {}\" " , artifact );
9286
93- var request = new ArtifactRequest ();
94- request .setArtifact (artifact );
95- request .setRepositories (remoteRepositories );
87+ var request = new ArtifactRequest ()
88+ .setArtifact (artifact )
89+ .setRepositories (computeRemoteRepositories () );
9690
97- ArtifactResult result ;
91+ ArtifactResult artifactResult ;
92+ Exception cause = null ;
9893
9994 try {
100- result = repositorySystem .resolveArtifact (repositorySystemSession , request );
95+ artifactResult = repositorySystem .resolveArtifact (repositorySystemSession , request );
10196 } catch (ArtifactResolutionException ex ) {
102- log .debug ("Discarding internal exception while resolving \" {}\" " , artifact , ex );
103- result = ex .getResult ();
97+ log .debug ("Handling resolution exception" , ex );
98+ artifactResult = ex .getResult ();
99+ cause = ex ;
104100 }
105101
106- // Looks like we can get resolution exceptions even if things resolve correctly, as it appears
107- // to raise for the local repository first.
108- // If this happens, don't bother raising or reporting it as it is normal behaviour. If anything
109- // else goes wrong, then we can panic about it.
110- if (result .isMissing ()) {
111- throw mapExceptions (
112- "Failed to resolve artifact \" " + artifact + "\" " ,
113- result .getExceptions ()
102+ if (cause != null || !artifactResult .isResolved ()) {
103+ var ex = new ResolutionException (
104+ "Failed to resolve artifact "
105+ + artifact
106+ + ". Check the coordinates and connection, and try again. Cause was: "
107+ + cause ,
108+ cause
114109 );
115- }
116110
117- reportWarnings (result .getExceptions ());
111+ artifactResult .getExceptions ().forEach (ex ::addSuppressed );
112+ throw ex ;
113+ }
118114
119- // This shouldn't happen, but I do not trust that Aether isn't hiding some wild edge cases
120- // for me here.
121- return requireNonNull (
122- result .getArtifact (),
123- () -> "No result was returned by Aether while resolving artifact \" " + artifact + "\" "
124- );
115+ return Objects .requireNonNull (artifactResult .getArtifact ());
125116 }
126117
127118 Collection <Artifact > resolveDependencies (
128119 List <Dependency > dependencies ,
129120 Set <String > allowedDependencyScopes
130- ) {
131- var collectRequest = new CollectRequest ();
132- collectRequest .setDependencies (dependencies );
133- collectRequest .setRepositories (remoteRepositories );
134-
135- var dependencyRequest = new DependencyRequest ();
136- dependencyRequest .setCollectRequest (collectRequest );
137- var filter = new ScopeDependencyFilter (
138- /* included: */ allowedDependencyScopes ,
139- /* excluded */ List .of ()
140- );
141- dependencyRequest .setFilter (filter );
121+ ) throws ResolutionException {
122+ if (dependencies .isEmpty ()) {
123+ return List .of ();
124+ }
142125
143126 log .debug (
144127 "Resolving {} with {} {} - {}" ,
@@ -148,67 +131,78 @@ Collection<Artifact> resolveDependencies(
148131 dependencies
149132 );
150133
134+ var dependencyRequest = new DependencyRequest ()
135+ .setCollectRequest (new CollectRequest ()
136+ .setDependencies (dependencies )
137+ .setRepositories (computeRemoteRepositories ()))
138+ .setFilter (new InclusiveScopeDependencyFilter (allowedDependencyScopes ));
139+
151140 DependencyResult dependencyResult ;
141+ Exception cause = null ;
152142
153143 try {
154144 dependencyResult = repositorySystem
155145 .resolveDependencies (repositorySystemSession , dependencyRequest );
156146 } catch (DependencyResolutionException ex ) {
157- log .debug ("Discarding internal exception while resolving {}" , dependencies , ex );
147+ log .debug ("Handling resolution exception" , ex );
158148 dependencyResult = ex .getResult ();
149+ cause = ex ;
159150 }
160151
161- var artifacts = new ArrayList <Artifact >();
162- var exceptions = new ArrayList <>(dependencyResult .getCollectExceptions ());
163-
164- for (var artifactResult : dependencyResult .getArtifactResults ()) {
165- var artifact = artifactResult .getArtifact ();
166- if (artifactResult .isResolved () && artifact != null ) {
167- log .debug ("Resolution of dependencies returned artifact \" {}\" " , artifact );
168- artifacts .add (artifact );
152+ // Handle the multiple cases where we might have failed.
153+ if (cause != null || !dependencyResult .getCollectExceptions ().isEmpty ()) {
154+ // TODO(ascopes): should we limit the number of things output here?
155+ var failedGavs = dependencyResult .getArtifactResults ().stream ()
156+ .filter (not (ArtifactResult ::isResolved ))
157+ .map (ArtifactResult ::getArtifact )
158+ .map (Artifact ::toString )
159+ .collect (Collectors .joining (", " ));
160+
161+ String errorMessage ;
162+
163+ if (failedGavs .isEmpty ()) {
164+ errorMessage = "Failed to resolve dependencies. Cause was: " + cause ;
165+ } else {
166+ errorMessage = "Failed to resolve dependencies: " + failedGavs
167+ + ". Check the direct and transitive coordinates, and network connection, "
168+ + "then try again. Cause was: "
169+ + cause ;
169170 }
170171
171- if (artifactResult .isMissing ()) {
172- log .debug ("Could not find artifact \" {}\" during dependency resolution" , artifact );
173- }
172+ var ex = new ResolutionException (errorMessage , cause );
173+ dependencyResult .getCollectExceptions ().forEach (ex ::addSuppressed );
174+ dependencyResult .getArtifactResults ().stream ()
175+ .map (ArtifactResult ::getExceptions )
176+ .flatMap (List ::stream )
177+ .forEach (ex ::addSuppressed );
174178
175- exceptions . addAll ( artifactResult . getExceptions ()) ;
179+ throw ex ;
176180 }
177181
178- reportWarnings (exceptions );
179-
180- return Collections .unmodifiableList (artifacts );
182+ return dependencyResult .getArtifactResults ()
183+ .stream ()
184+ .map (ArtifactResult ::getArtifact )
185+ .map (Objects ::requireNonNull )
186+ .toList ();
181187 }
182188
183- private ResolutionException mapExceptions (String message , Collection <Exception > causes ) {
184- var causeIterator = causes .iterator ();
185-
186- // Assumption: this is always possible. If it isn't, we screwed up somewhere.
187- var cause = causeIterator .next ();
188- var exception = new ResolutionException (
189- message
190- + " - resolution failed with "
191- + StringUtils .pluralize (causes .size (), "error: " , "errors - first was: " )
192- + cause .getMessage (),
193- cause
189+ // Some historical context of why we do this in a very specific way:
190+ //
191+ // Prior to v2.12.0, we used the ProjectBuildingRequest on the MavenSession
192+ // and used RepositoryUtils.toRepos to create the repository list. GH-579
193+ // was raised to report that the <repositories> block in the POM was being
194+ // ignored. This appears to be due to the project building request only
195+ // looking at what is in ~/.m2/settings.xml. The current project remote
196+ // repositories seems to be what we need to use instead here.
197+ //
198+ // As of 5.0.2, we use .newResolutionRepositories to do this as it ensures
199+ // certain networking and authentication configurations are propagated
200+ // correctly without relying on Maven exposing the final configuration to
201+ // us immediately.
202+ private List <RemoteRepository > computeRemoteRepositories () {
203+ return repositorySystem .newResolutionRepositories (
204+ repositorySystemSession ,
205+ mavenProject .getRemoteProjectRepositories ()
194206 );
195- causeIterator .forEachRemaining (exception ::addSuppressed );
196- return exception ;
197- }
198-
199- private void reportWarnings (Iterable <? extends Exception > exceptions ) {
200- exceptions .forEach (exception -> {
201- // We purposely log the warning class and message twice, since exception tracebacks are
202- // hidden unless Maven was invoked with --errors.
203- //noinspection LoggingPlaceholderCountMatchesArgumentCount
204- log .debug (
205- "Dependency resolution warning was reported. "
206- + "This might be okay, or it could indicate a further issue - {}: {}" ,
207- exception .getClass ().getName (),
208- exception .getMessage (),
209- exception
210- );
211- });
212207 }
213208}
214-
0 commit comments