|
25 | 25 | import java.util.LinkedHashMap; |
26 | 26 | import java.util.List; |
27 | 27 | import java.util.Map; |
| 28 | +import java.util.Objects; |
28 | 29 | import java.util.Properties; |
29 | 30 | import java.util.function.Function; |
30 | 31 | import java.util.stream.Collectors; |
|
37 | 38 | import org.apache.maven.api.feature.Features; |
38 | 39 | import org.apache.maven.api.model.Activation; |
39 | 40 | import org.apache.maven.api.model.Dependency; |
| 41 | +import org.apache.maven.api.model.DependencyManagement; |
40 | 42 | import org.apache.maven.api.model.DistributionManagement; |
41 | 43 | import org.apache.maven.api.model.Model; |
42 | 44 | import org.apache.maven.api.model.ModelBase; |
@@ -459,6 +461,10 @@ private ModelBuilderResult buildModel(RepositorySystemSession session, MavenProj |
459 | 461 |
|
460 | 462 | static Model transformNonPom(Model model, MavenProject project) { |
461 | 463 | boolean preserveModelVersion = model.isPreserveModelVersion(); |
| 464 | + String packaging = model.getPackaging(); |
| 465 | + |
| 466 | + // Inline packaging-activated profiles into the model |
| 467 | + model = inlinePackagingActivatedProfiles(model, packaging); |
462 | 468 |
|
463 | 469 | Model.Builder builder = prune( |
464 | 470 | Model.newBuilder(model, true) |
@@ -490,7 +496,7 @@ static Model transformNonPom(Model model, MavenProject project) { |
490 | 496 | return model; |
491 | 497 | } |
492 | 498 |
|
493 | | - private static Model transformBom(Model model, MavenProject project) { |
| 499 | + static Model transformBom(Model model, MavenProject project) { |
494 | 500 | boolean preserveModelVersion = model.isPreserveModelVersion(); |
495 | 501 |
|
496 | 502 | Model.Builder builder = prune( |
@@ -554,6 +560,125 @@ private static void warnNotDowngraded(MavenProject project) { |
554 | 560 | + "attribute on the <project> element of your POM."); |
555 | 561 | } |
556 | 562 |
|
| 563 | + /** |
| 564 | + * Inlines packaging-activated profiles into the model. |
| 565 | + * <p> |
| 566 | + * When a profile is activated by packaging and the packaging matches the project's packaging, |
| 567 | + * the profile's content (dependencies, dependency management, repositories) is merged into |
| 568 | + * the main model and the profile is removed. This ensures consistent behavior across all |
| 569 | + * tools consuming the POM, since packaging activation is a 4.1.0+ feature not available |
| 570 | + * in Maven 3 or other tools like Gradle. |
| 571 | + * <p> |
| 572 | + * If the profile has other activation conditions besides packaging, only the packaging |
| 573 | + * part is stripped from the activation; the profile's content is <b>not</b> inlined to |
| 574 | + * preserve AND semantics (the content remains gated by the remaining conditions). |
| 575 | + * <p> |
| 576 | + * Profiles with a non-matching packaging activation are dropped entirely, since they |
| 577 | + * can never activate for this artifact's fixed packaging and their presence would block |
| 578 | + * model version downgrade to 4.0.0. |
| 579 | + * <p> |
| 580 | + * Non-transitive scope dependencies (test, provided, system) from inlined profiles are |
| 581 | + * filtered out to prevent leakage into the consumer POM. |
| 582 | + * |
| 583 | + * @param model the model to process |
| 584 | + * @param packaging the project's packaging type |
| 585 | + * @return the model with packaging-activated profiles inlined |
| 586 | + */ |
| 587 | + static Model inlinePackagingActivatedProfiles(Model model, String packaging) { |
| 588 | + List<Profile> remainingProfiles = new ArrayList<>(); |
| 589 | + List<Dependency> additionalDeps = new ArrayList<>(); |
| 590 | + List<Dependency> additionalManagedDeps = new ArrayList<>(); |
| 591 | + List<Repository> additionalRepos = new ArrayList<>(); |
| 592 | + |
| 593 | + for (Profile profile : model.getProfiles()) { |
| 594 | + Activation activation = profile.getActivation(); |
| 595 | + if (activation != null && activation.getPackaging() != null) { |
| 596 | + if (Objects.equals(activation.getPackaging(), packaging)) { |
| 597 | + Activation strippedActivation = stripPackagingActivation(activation); |
| 598 | + if (strippedActivation != null) { |
| 599 | + // Keep the profile but remove the packaging activation part |
| 600 | + // Do not inline its contents since it has other activation conditions |
| 601 | + remainingProfiles.add(profile.withActivation(strippedActivation)); |
| 602 | + } else { |
| 603 | + // Packaging is the ONLY condition. |
| 604 | + // Inline profile content into the model |
| 605 | + additionalDeps.addAll(profile.getDependencies()); |
| 606 | + if (profile.getDependencyManagement() != null) { |
| 607 | + additionalManagedDeps.addAll( |
| 608 | + profile.getDependencyManagement().getDependencies()); |
| 609 | + } |
| 610 | + additionalRepos.addAll(profile.getRepositories()); |
| 611 | + } |
| 612 | + } else { |
| 613 | + // Packaging does not match — drop the profile entirely |
| 614 | + } |
| 615 | + } else { |
| 616 | + // No packaging activation — keep the profile as-is |
| 617 | + remainingProfiles.add(profile); |
| 618 | + } |
| 619 | + } |
| 620 | + |
| 621 | + // Merge additional dependencies into the model, deduplicating by key |
| 622 | + if (!additionalDeps.isEmpty()) { |
| 623 | + additionalDeps.removeIf(DefaultConsumerPomBuilder::hasDependencyScope); |
| 624 | + Map<String, Dependency> mergedDeps = new LinkedHashMap<>(); |
| 625 | + for (Dependency dep : model.getDependencies()) { |
| 626 | + mergedDeps.put(getDependencyKey(dep), dep); |
| 627 | + } |
| 628 | + for (Dependency dep : additionalDeps) { |
| 629 | + mergedDeps.putIfAbsent(getDependencyKey(dep), dep); |
| 630 | + } |
| 631 | + model = model.withDependencies(mergedDeps.values()); |
| 632 | + } |
| 633 | + |
| 634 | + // Merge additional managed dependencies into the model, deduplicating by key |
| 635 | + if (!additionalManagedDeps.isEmpty()) { |
| 636 | + // Filter out import-scoped entries — they are BOM references that get |
| 637 | + // flattened during resolution and must not reappear in the consumer POM |
| 638 | + additionalManagedDeps.removeIf(dep -> "import".equals(dep.getScope())); |
| 639 | + DependencyManagement dm = model.getDependencyManagement(); |
| 640 | + Map<String, Dependency> mergedManagedDeps = new LinkedHashMap<>(); |
| 641 | + if (dm != null) { |
| 642 | + for (Dependency dep : dm.getDependencies()) { |
| 643 | + mergedManagedDeps.put(getDependencyKey(dep), dep); |
| 644 | + } |
| 645 | + } |
| 646 | + for (Dependency dep : additionalManagedDeps) { |
| 647 | + mergedManagedDeps.putIfAbsent(getDependencyKey(dep), dep); |
| 648 | + } |
| 649 | + model = model.withDependencyManagement((dm != null ? dm : DependencyManagement.newInstance()) |
| 650 | + .withDependencies(mergedManagedDeps.values())); |
| 651 | + } |
| 652 | + |
| 653 | + // Merge additional repositories into the model, deduplicating by id |
| 654 | + if (!additionalRepos.isEmpty()) { |
| 655 | + Map<String, Repository> mergedRepos = new LinkedHashMap<>(); |
| 656 | + for (Repository repo : model.getRepositories()) { |
| 657 | + mergedRepos.put(repo.getId(), repo); |
| 658 | + } |
| 659 | + for (Repository repo : additionalRepos) { |
| 660 | + mergedRepos.putIfAbsent(repo.getId(), repo); |
| 661 | + } |
| 662 | + model = model.withRepositories(mergedRepos.values()); |
| 663 | + } |
| 664 | + |
| 665 | + return model.withProfiles(remainingProfiles); |
| 666 | + } |
| 667 | + |
| 668 | + /** |
| 669 | + * Strips the packaging activation from an activation, returning the remaining activation |
| 670 | + * or {@code null} if packaging was the only activation condition. |
| 671 | + */ |
| 672 | + private static Activation stripPackagingActivation(Activation activation) { |
| 673 | + Activation stripped = |
| 674 | + Activation.newBuilder(activation, true).packaging(null).build(); |
| 675 | + // Check if the remaining activation has any other conditions |
| 676 | + if (isActivationEmpty(stripped)) { |
| 677 | + return null; |
| 678 | + } |
| 679 | + return stripped; |
| 680 | + } |
| 681 | + |
557 | 682 | /** |
558 | 683 | * Strips {@code executable()} conditions from profile activations. |
559 | 684 | * <p> |
|
0 commit comments