|
29 | 29 | import java.util.LinkedHashMap; |
30 | 30 | import java.util.List; |
31 | 31 | import java.util.Map; |
| 32 | +import java.util.function.Function; |
| 33 | +import java.util.function.Predicate; |
32 | 34 | import java.util.stream.Collectors; |
33 | 35 |
|
34 | 36 | import org.apache.maven.RepositoryUtils; |
|
51 | 53 | import org.codehaus.plexus.logging.Logger; |
52 | 54 | import org.codehaus.plexus.util.xml.Xpp3Dom; |
53 | 55 | import org.eclipse.aether.ConfigurationProperties; |
| 56 | +import org.eclipse.aether.RepositoryException; |
54 | 57 | import org.eclipse.aether.RepositorySystem; |
55 | 58 | import org.eclipse.aether.RepositorySystemSession; |
| 59 | +import org.eclipse.aether.artifact.Artifact; |
| 60 | +import org.eclipse.aether.artifact.DefaultArtifact; |
| 61 | +import org.eclipse.aether.collection.DependencyCollectionContext; |
| 62 | +import org.eclipse.aether.collection.VersionFilter; |
56 | 63 | import org.eclipse.aether.repository.LocalRepository; |
57 | 64 | import org.eclipse.aether.repository.LocalRepositoryManager; |
58 | 65 | import org.eclipse.aether.repository.RepositoryPolicy; |
59 | 66 | import org.eclipse.aether.repository.WorkspaceReader; |
60 | 67 | import org.eclipse.aether.resolution.ResolutionErrorPolicy; |
61 | 68 | import org.eclipse.aether.util.ConfigUtils; |
| 69 | +import org.eclipse.aether.util.graph.version.ChainedVersionFilter; |
| 70 | +import org.eclipse.aether.util.graph.version.ContextualSnapshotVersionFilter; |
| 71 | +import org.eclipse.aether.util.graph.version.HighestVersionFilter; |
| 72 | +import org.eclipse.aether.util.graph.version.LowestVersionFilter; |
| 73 | +import org.eclipse.aether.util.graph.version.PredicateVersionFilter; |
| 74 | +import org.eclipse.aether.util.graph.version.SnapshotVersionFilter; |
62 | 75 | import org.eclipse.aether.util.listener.ChainedRepositoryListener; |
63 | 76 | import org.eclipse.aether.util.repository.AuthenticationBuilder; |
64 | 77 | import org.eclipse.aether.util.repository.ChainedLocalRepositoryManager; |
65 | 78 | import org.eclipse.aether.util.repository.DefaultAuthenticationSelector; |
66 | 79 | import org.eclipse.aether.util.repository.DefaultMirrorSelector; |
67 | 80 | import org.eclipse.aether.util.repository.DefaultProxySelector; |
68 | 81 | import org.eclipse.aether.util.repository.SimpleResolutionErrorPolicy; |
| 82 | +import org.eclipse.aether.util.version.GenericVersionScheme; |
| 83 | +import org.eclipse.aether.version.InvalidVersionSpecificationException; |
| 84 | +import org.eclipse.aether.version.Version; |
| 85 | +import org.eclipse.aether.version.VersionConstraint; |
69 | 86 | import org.eclipse.sisu.Nullable; |
70 | 87 |
|
71 | 88 | /** |
@@ -110,6 +127,28 @@ public class DefaultRepositorySystemSessionFactory { |
110 | 127 | */ |
111 | 128 | private static final String MAVEN_REPO_LOCAL_RECORD_REVERSE_TREE = "maven.repo.local.recordReverseTree"; |
112 | 129 |
|
| 130 | + /** |
| 131 | + * User property for version filter expression used in session, applied to resolving ranges: a semicolon separated |
| 132 | + * list of filters to apply. By default, no version filter is applied (like in Maven 3). |
| 133 | + * <br/> |
| 134 | + * Supported filters: |
| 135 | + * <ul> |
| 136 | + * <li>{@code "h"} or {@code "h(num[@G[:A]])"} - highest version or top list of highest ones filter</li> |
| 137 | + * <li>{@code "l"} or {@code "l(num[@G[:A]])"} - lowest version or bottom list of lowest ones filter</li> |
| 138 | + * <li>{@code "s"} - contextual snapshot filter</li> |
| 139 | + * <li>{@code "ns"} - unconditional snapshot filter (no snapshots selected from ranges)</li> |
| 140 | + * <li>{@code "e(G:A:V)"} - predicate filter (excludes G:A:V from range, if hit, V can be version constraint)</li> |
| 141 | + * <li>{@code "i(G:A:V)"} - predicate filter (includes G:A:V from range, if hit, V can be version constraint)</li> |
| 142 | + * </ul> |
| 143 | + * Example filter expression: <code>"h(5);s;e(org.foo:bar:1)</code> will cause: ranges are filtered for "top 5" (instead |
| 144 | + * full range), snapshots are banned if root project is not a snapshot, and if range for <code>org.foo:bar</code> is |
| 145 | + * being processed, version 1 is omitted. Value in this property builds |
| 146 | + * <code>org.eclipse.aether.collection.VersionFilter</code> instance. |
| 147 | + * |
| 148 | + * @since 3.10.0 |
| 149 | + */ |
| 150 | + private static final String MAVEN_VERSION_FILTER = "maven.session.versionFilter"; |
| 151 | + |
113 | 152 | private static final String MAVEN_RESOLVER_TRANSPORT_KEY = "maven.resolver.transport"; |
114 | 153 |
|
115 | 154 | private static final String MAVEN_RESOLVER_TRANSPORT_DEFAULT = "default"; |
@@ -154,6 +193,8 @@ public class DefaultRepositorySystemSessionFactory { |
154 | 193 | @Inject |
155 | 194 | private RuntimeInformation runtimeInformation; |
156 | 195 |
|
| 196 | + private final GenericVersionScheme versionScheme = new GenericVersionScheme(); |
| 197 | + |
157 | 198 | @SuppressWarnings("checkstyle:methodlength") |
158 | 199 | public RepositorySystemSession.SessionBuilder newRepositorySession(MavenExecutionRequest request) { |
159 | 200 | RepositorySystemSession.SessionBuilder mainSessionBuilder = MavenRepositorySystemUtils.newSession(repoSystem); |
@@ -208,6 +249,11 @@ public RepositorySystemSession.SessionBuilder newRepositorySession(MavenExecutio |
208 | 249 | } |
209 | 250 | } |
210 | 251 |
|
| 252 | + VersionFilter versionFilter = buildVersionFilter((String) configProps.get(MAVEN_VERSION_FILTER)); |
| 253 | + if (versionFilter != null) { |
| 254 | + mainSessionBuilder.setVersionFilter(versionFilter); |
| 255 | + } |
| 256 | + |
211 | 257 | DefaultMirrorSelector mirrorSelector = new DefaultMirrorSelector(); |
212 | 258 | for (Mirror mirror : request.getMirrors()) { |
213 | 259 | mirrorSelector.add( |
@@ -438,6 +484,114 @@ public static Path resolve(String string) { |
438 | 484 | } |
439 | 485 | } |
440 | 486 |
|
| 487 | + /** |
| 488 | + * Visible for testing. |
| 489 | + */ |
| 490 | + VersionFilter buildVersionFilter(String filterExpression) { |
| 491 | + ArrayList<VersionFilter> filters = new ArrayList<>(); |
| 492 | + if (filterExpression != null) { |
| 493 | + List<String> expressions = Arrays.stream(filterExpression.split(";")) |
| 494 | + .filter(s -> !s.trim().isEmpty()) |
| 495 | + .collect(Collectors.toList()); |
| 496 | + for (String expression : expressions) { |
| 497 | + if ("h".equals(expression)) { |
| 498 | + filters.add(new HighestVersionFilter()); |
| 499 | + } else if ("l".equals(expression)) { |
| 500 | + filters.add(new LowestVersionFilter()); |
| 501 | + } else if ((expression.startsWith("h(") || expression.startsWith("l(")) && expression.endsWith(")")) { |
| 502 | + Function<Integer, VersionFilter> filterSupplier = |
| 503 | + n -> expression.startsWith("h(") ? new HighestVersionFilter(n) : new LowestVersionFilter(n); |
| 504 | + String inner = expression.substring(2, expression.length() - 1); |
| 505 | + int num; |
| 506 | + String g; |
| 507 | + String a; |
| 508 | + if (inner.contains("@")) { |
| 509 | + num = Integer.parseInt(inner.substring(0, inner.indexOf('@'))); |
| 510 | + String remainder = inner.substring(inner.indexOf('@') + 1); |
| 511 | + if (remainder.contains(":")) { |
| 512 | + g = remainder.substring(0, remainder.indexOf(':')); |
| 513 | + a = remainder.substring(remainder.indexOf(':') + 1); |
| 514 | + } else { |
| 515 | + g = remainder; |
| 516 | + a = null; |
| 517 | + } |
| 518 | + } else { |
| 519 | + num = Integer.parseInt(inner); |
| 520 | + g = null; |
| 521 | + a = null; |
| 522 | + } |
| 523 | + if (g == null) { |
| 524 | + filters.add(filterSupplier.apply(num)); |
| 525 | + } else { |
| 526 | + VersionFilter versionFilter = filterSupplier.apply(num); |
| 527 | + filters.add(new VersionFilter() { |
| 528 | + @Override |
| 529 | + public void filterVersions(VersionFilterContext context) throws RepositoryException { |
| 530 | + Artifact dependencyArtifact = |
| 531 | + context.getDependency().getArtifact(); |
| 532 | + if (g.equals(dependencyArtifact.getGroupId()) |
| 533 | + && (a == null || a.equals(dependencyArtifact.getArtifactId()))) { |
| 534 | + versionFilter.filterVersions(context); |
| 535 | + } |
| 536 | + } |
| 537 | + |
| 538 | + @Override |
| 539 | + public VersionFilter deriveChildFilter(DependencyCollectionContext context) { |
| 540 | + return this; |
| 541 | + } |
| 542 | + }); |
| 543 | + } |
| 544 | + } else if ("s".equals(expression)) { |
| 545 | + filters.add(new ContextualSnapshotVersionFilter()); |
| 546 | + } else if ("ns".equals(expression)) { |
| 547 | + filters.add(new SnapshotVersionFilter()); |
| 548 | + } else if ((expression.startsWith("e(") || (expression.startsWith("i("))) && expression.endsWith(")")) { |
| 549 | + Artifact artifact = new DefaultArtifact(expression.substring(2, expression.length() - 1)); |
| 550 | + VersionConstraint versionConstraint = parseVersionConstraint(artifact.getVersion()); |
| 551 | + Predicate<Artifact> predicate = a -> { |
| 552 | + if (artifact.getGroupId().equals(a.getGroupId()) |
| 553 | + && artifact.getArtifactId().equals(a.getArtifactId())) { |
| 554 | + if (expression.startsWith("e(")) { |
| 555 | + // exclude |
| 556 | + return !versionConstraint.containsVersion(parseVersion(a.getVersion())); |
| 557 | + } else { |
| 558 | + // include |
| 559 | + return versionConstraint.containsVersion(parseVersion(a.getVersion())); |
| 560 | + } |
| 561 | + } |
| 562 | + return true; |
| 563 | + }; |
| 564 | + filters.add(new PredicateVersionFilter(predicate)); |
| 565 | + } else { |
| 566 | + throw new IllegalArgumentException("Unsupported filter expression: " + expression); |
| 567 | + } |
| 568 | + } |
| 569 | + } |
| 570 | + if (filters.isEmpty()) { |
| 571 | + return null; |
| 572 | + } else if (filters.size() == 1) { |
| 573 | + return filters.get(0); |
| 574 | + } else { |
| 575 | + return ChainedVersionFilter.newInstance(filters); |
| 576 | + } |
| 577 | + } |
| 578 | + |
| 579 | + private Version parseVersion(String spec) { |
| 580 | + try { |
| 581 | + return versionScheme.parseVersion(spec); |
| 582 | + } catch (InvalidVersionSpecificationException e) { |
| 583 | + throw new RuntimeException(e); |
| 584 | + } |
| 585 | + } |
| 586 | + |
| 587 | + private VersionConstraint parseVersionConstraint(String spec) { |
| 588 | + try { |
| 589 | + return versionScheme.parseVersionConstraint(spec); |
| 590 | + } catch (InvalidVersionSpecificationException e) { |
| 591 | + throw new RuntimeException(e); |
| 592 | + } |
| 593 | + } |
| 594 | + |
441 | 595 | private Map<?, ?> getPropertiesFromRequestedProfiles(MavenExecutionRequest request) { |
442 | 596 |
|
443 | 597 | List<String> activeProfileId = request.getActiveProfiles(); |
|
0 commit comments