This is an automated email from the ASF dual-hosted git repository. royteeuwen pushed a commit to branch feature/multi-feature-and-osgi-dedup in repository https://gitbox.apache.org/repos/asf/sling-feature-launcher-maven-plugin.git
commit 07c0dd40425e477543a7b6001797eb9d77103d57 Author: Roy Teeuwen <[email protected]> AuthorDate: Tue Jun 2 23:26:27 2026 +0200 SLING-13233: multi-feature aggregation + OSGi BSN collision detection Add to <launch>: - <features> / <featureFiles> for aggregating multiple input features. Order matters for the launcher's LATEST / FIRST overrides. - <osgiBsnCollisionDetection> (boolean) maps to the launcher's --osgi-bsn-collision-detection flag (SLING-13217 / 1.3.6). - <artifactClashOverrides> maps to the launcher's -C flag. Relax Launch.validate(): combining <feature> + <featureFile> is now valid (both contribute as additive inputs). At least one of feature / featureFile / features / featureFiles must still be set. Default featureLauncherVersion bumped from 1.3.4 to 1.3.6 so consumers pick up --osgi-bsn-collision-detection without overriding. --- .../sling/maven/feature/launcher/Launch.java | 70 ++++++++++++++++++++-- .../sling/maven/feature/launcher/StartMojo.java | 34 +++++++++-- .../sling/maven/feature/launcher/LaunchTest.java | 33 +++++++++- 3 files changed, 125 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/apache/sling/maven/feature/launcher/Launch.java b/src/main/java/org/apache/sling/maven/feature/launcher/Launch.java index b797316..3c94142 100644 --- a/src/main/java/org/apache/sling/maven/feature/launcher/Launch.java +++ b/src/main/java/org/apache/sling/maven/feature/launcher/Launch.java @@ -35,7 +35,11 @@ public class Launch { private String id; private Dependency feature; + private List<Dependency> features = new ArrayList<>(); private String featureFile; + private List<String> featureFiles = new ArrayList<>(); + private boolean osgiBsnCollisionDetection; + private List<String> artifactClashOverrides = new ArrayList<>(); private LauncherArguments launcherArguments = new LauncherArguments(); private int startTimeoutSeconds = 30; private boolean skip = false; @@ -66,6 +70,44 @@ public class Launch { this.featureFile = featureFile; } + /** Additional features (Maven coords) to aggregate alongside {@link #getFeature()}. */ + public List<Dependency> getFeatures() { + return features != null ? features : Collections.emptyList(); + } + + public void setFeatures(List<Dependency> features) { + this.features = features; + } + + /** Additional feature files to aggregate alongside {@link #getFeatureFile()}. */ + public List<File> getFeatureFiles() { + return featureFiles == null + ? Collections.emptyList() + : featureFiles.stream().map(File::new).collect(java.util.stream.Collectors.toList()); + } + + public void setFeatureFiles(List<String> featureFiles) { + this.featureFiles = featureFiles; + } + + /** @return whether to enable OSGi BSN collision detection in the launcher. Default false. */ + public boolean isOsgiBsnCollisionDetection() { + return osgiBsnCollisionDetection; + } + + public void setOsgiBsnCollisionDetection(boolean enabled) { + this.osgiBsnCollisionDetection = enabled; + } + + /** Maven-coord clash overrides passed to the launcher as {@code -C} flags. */ + public List<String> getArtifactClashOverrides() { + return artifactClashOverrides == null ? Collections.emptyList() : artifactClashOverrides; + } + + public void setArtifactClashOverrides(List<String> artifactClashOverrides) { + this.artifactClashOverrides = artifactClashOverrides; + } + public LauncherArguments getLauncherArguments() { return launcherArguments; } @@ -118,17 +160,37 @@ public class Launch { boolean hasFeature = feature != null; boolean hasFeatureFile = featureFile != null && !featureFile.trim().isEmpty(); + boolean hasFeatures = features != null && !features.isEmpty(); + boolean hasFeatureFiles = featureFiles != null && !featureFiles.isEmpty(); - if (hasFeature && hasFeatureFile) - throwInvalid("Only one of 'feature' and 'featureFile' is allowed, but both are set"); - - if (!hasFeature && !hasFeatureFile) throwInvalid("Neither 'feature' nor 'featureFile' are set"); + if (!hasFeature && !hasFeatureFile && !hasFeatures && !hasFeatureFiles) { + throwInvalid("None of 'feature', 'featureFile', 'features', 'featureFiles' are set"); + } if (hasFeatureFile && !new File(featureFile).exists()) throwInvalid("Feature file '" + featureFile + "' does not exist"); if (hasFeature && !"slingosgifeature".equals(feature.getType())) throwInvalid("type must be 'slingosgifeature' but is '" + feature.getType() + "'"); + + if (hasFeatures) { + for (final Dependency d : features) { + if (!"slingosgifeature".equals(d.getType())) { + throwInvalid("features[].type must be 'slingosgifeature' but is '" + d.getType() + "'"); + } + } + } + + if (hasFeatureFiles) { + for (final String f : featureFiles) { + if (f == null || f.trim().isEmpty()) { + throwInvalid("featureFiles[] entry is blank"); + } + if (!new File(f).exists()) { + throwInvalid("Feature file '" + f + "' does not exist"); + } + } + } } private void throwInvalid(String reason) { diff --git a/src/main/java/org/apache/sling/maven/feature/launcher/StartMojo.java b/src/main/java/org/apache/sling/maven/feature/launcher/StartMojo.java index ad2fd4f..dfc7493 100644 --- a/src/main/java/org/apache/sling/maven/feature/launcher/StartMojo.java +++ b/src/main/java/org/apache/sling/maven/feature/launcher/StartMojo.java @@ -81,7 +81,7 @@ public class StartMojo extends AbstractMojo { /** * The version of the <a href="https://github.com/apache/sling-org-apache-sling-feature-launcher">Sling Feature Launcher</a> to use. */ - @Parameter(required = true, defaultValue = "1.3.4") + @Parameter(required = true, defaultValue = "1.3.6") private String featureLauncherVersion; /** @@ -215,12 +215,23 @@ public class StartMojo extends AbstractMojo { launch.validate(); - File featureFile = launch.getFeature() + // Aggregate every primary + additional feature into a single ordered list + // of files to pass to the launcher CLI as separate `-f` flags. Order + // matters: it determines which feature wins under FIRST_WINS / LAST_WINS + // and how merge handlers see the input. + final List<File> featureFiles = new ArrayList<>(); + launch.getFeature() .map(this::toArtifact) .map(a -> uncheckedResolveArtifact(repositorySession, a)) .map(r -> r.getArtifact().getFile()) - .orElseGet(() -> launch.getFeatureFile() - .get()); // the Launch is guaranteed to either have a feature or a featureFile set + .ifPresent(featureFiles::add); + launch.getFeatureFile().ifPresent(featureFiles::add); + for (org.apache.maven.model.Dependency d : launch.getFeatures()) { + featureFiles.add(uncheckedResolveArtifact(repositorySession, toArtifact(d)) + .getArtifact() + .getFile()); + } + featureFiles.addAll(launch.getFeatureFiles()); String javahome = System.getenv(JAVA_HOME); if (javahome == null || javahome.isEmpty()) { @@ -300,11 +311,22 @@ public class StartMojo extends AbstractMojo { repositoryUrls.forEach(joiner::add); args.add(joiner.toString()); - args.add("-f"); - args.add(featureFile.getAbsolutePath()); + for (File f : featureFiles) { + args.add("-f"); + args.add(f.getAbsolutePath()); + } args.add("-p"); args.add(launch.getId()); + if (launch.isOsgiBsnCollisionDetection()) { + args.add("--osgi-bsn-collision-detection"); + } + + for (String override : launch.getArtifactClashOverrides()) { + args.add("-C"); + args.add(override); + } + for (Map.Entry<String, String> frameworkProperty : launch.getLauncherArguments().getFrameworkProperties().entrySet()) { args.add("-D"); diff --git a/src/test/java/org/apache/sling/maven/feature/launcher/LaunchTest.java b/src/test/java/org/apache/sling/maven/feature/launcher/LaunchTest.java index a41dac3..e67152a 100644 --- a/src/test/java/org/apache/sling/maven/feature/launcher/LaunchTest.java +++ b/src/test/java/org/apache/sling/maven/feature/launcher/LaunchTest.java @@ -112,9 +112,12 @@ public class LaunchTest { launch.validate(); } - @Test(expected = IllegalArgumentException.class) - public void invalidLaunch_bothFeatureAndFeatureFile() { + @Test + public void validLaunch_bothFeatureAndFeatureFile() { + // Combining a feature (Maven coords) and a featureFile (local file path) is now + // allowed — they are both additive inputs to the launcher's feature aggregation, + // not mutually exclusive single-feature selectors. Launch launch = new Launch(); launch.setId("feature"); launch.setFeature(validDep); @@ -130,4 +133,30 @@ public class LaunchTest { launch.setFeatureFile(validFeatureFile.getAbsolutePath() + ".missing"); launch.validate(); } + + @Test + public void validLaunch_multipleFeatures() { + Launch launch = new Launch(); + launch.setId("feature"); + launch.setFeatures(java.util.Arrays.asList(validDep, validDep)); + launch.validate(); + } + + @Test(expected = IllegalArgumentException.class) + public void invalidLaunch_missingEntryInFeatureFiles() { + Launch launch = new Launch(); + launch.setId("feature"); + launch.setFeatureFiles(java.util.Arrays.asList( + validFeatureFile.getAbsolutePath(), validFeatureFile.getAbsolutePath() + ".missing")); + launch.validate(); + } + + @Test + public void validLaunch_osgiBsnCollisionDetection() { + Launch launch = new Launch(); + launch.setId("feature"); + launch.setFeatureFile(validFeatureFile.getAbsolutePath()); + launch.setOsgiBsnCollisionDetection(true); + launch.validate(); + } }
