From ed3f0a7270d4d9aabb82feafd50b253907168c4a Mon Sep 17 00:00:00 2001 From: robserm <55186973+robserm@users.noreply.github.com> Date: Wed, 27 Nov 2024 08:48:34 +0100 Subject: [PATCH] feat: support configuring feature description, copyright and required features --- README.md | 19 ++++ .../platform/internal/DefaultFeature.groovy | 8 ++ .../plugin/platform/internal/Feature.java | 22 +++- .../internal/config/ArtifactFeature.groovy | 103 +++++++++++------- .../platform/internal/util/FeatureUtil.groovy | 21 ++++ 5 files changed, 131 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 15dff51..f9d2fca 100644 --- a/README.md +++ b/README.md @@ -324,6 +324,25 @@ platform { Providing a feature ID is mandatory, but *name* and *version* may be omitted (the version defaults to the platform feature version). `plugin` and `bundle` can be used synonymously inside the `feature` block, but when using `bundle` you may experience problems when used in inner closures. +The feature may include also other required features. See an example below how to define such dependencies: + +```groovy + platform { + // define a feature + feature(id: 'platform.restclient', name: 'REST client dependencies', version: '1.0.0') { + + requires ( + featureName: "required.feature.name", + version: "1.0.0", + match: "greaterOrEqual" + ) + + //... the rest of the feature definition + + } +} +``` + ### Local dependencies You can easily add local JARs to the platform. **If the JAR is not an OSGi bundle yet**, you have add it on its own and at least provide **symbolicName** and **version**: diff --git a/src/main/groovy/org/standardout/gradle/plugin/platform/internal/DefaultFeature.groovy b/src/main/groovy/org/standardout/gradle/plugin/platform/internal/DefaultFeature.groovy index 1355eb2..101e360 100644 --- a/src/main/groovy/org/standardout/gradle/plugin/platform/internal/DefaultFeature.groovy +++ b/src/main/groovy/org/standardout/gradle/plugin/platform/internal/DefaultFeature.groovy @@ -27,6 +27,8 @@ class DefaultFeature implements Feature { String version String providerName String license + String description + String copyright List bundles = [] List includedFeatures = [] Project project @@ -59,4 +61,10 @@ class DefaultFeature implements Feature { includedFeatures == null ? [] : includedFeatures } + @Override + public Iterable getRequiredFeatures() + { + [] + } + } diff --git a/src/main/groovy/org/standardout/gradle/plugin/platform/internal/Feature.java b/src/main/groovy/org/standardout/gradle/plugin/platform/internal/Feature.java index 4797154..7aea806 100644 --- a/src/main/groovy/org/standardout/gradle/plugin/platform/internal/Feature.java +++ b/src/main/groovy/org/standardout/gradle/plugin/platform/internal/Feature.java @@ -33,9 +33,27 @@ public interface Feature { public String getProviderName(); public String getLicense(); - + + public String getDescription(); + + public String getCopyright(); + public Iterable getBundles(); public Iterable getIncludedFeatures(); - + + public Iterable getRequiredFeatures(); + + class RequiredFeature { + public final String featureName; + public final String version; + public final String match; + + + public RequiredFeature(String featureName, String version, String match) { + this.featureName = featureName; + this.version = version; + this.match = match; + } + } } diff --git a/src/main/groovy/org/standardout/gradle/plugin/platform/internal/config/ArtifactFeature.groovy b/src/main/groovy/org/standardout/gradle/plugin/platform/internal/config/ArtifactFeature.groovy index 12825c1..7cde339 100644 --- a/src/main/groovy/org/standardout/gradle/plugin/platform/internal/config/ArtifactFeature.groovy +++ b/src/main/groovy/org/standardout/gradle/plugin/platform/internal/config/ArtifactFeature.groovy @@ -31,41 +31,50 @@ import org.standardout.gradle.plugin.platform.internal.util.VersionUtil; /** * Represents the configuration of a platform feature. - * + * * @author Simon Templer */ class ArtifactFeature implements Feature { - + final Project project - + final String id final String label final String version final String providerName final String license - + final String description + final String copyright + /** * List of artifact references */ final List configArtifacts = [] - + /** * List of included features IDs */ final List configFeatures = [] - + + /** + * List of required features + */ + List requiredFeatures = [] + private String finalVersion - + ArtifactFeature(Project project, def featureNotation, Closure featureClosure) { this.project = project - + def id def label def version def providerName def license - + def description + def copyright + // extract basic feature information from feature notation if (featureNotation instanceof Map) { id = featureNotation.id @@ -73,11 +82,13 @@ class ArtifactFeature implements Feature { version = featureNotation.version providerName = featureNotation.provider license = featureNotation.license + description = featureNotation.description + copyright = featureNotation.copyright } else { // assume String id and default values String featureString = featureNotation as String - + //XXX support some kind of pattern? // for now just assume it's the id id = featureString @@ -86,14 +97,16 @@ class ArtifactFeature implements Feature { if (!id) { throw new IllegalStateException('A feature ID must be provided when defining a feature') } - - // default values and source adaptions + + // default values and source adaptions this.version = VersionUtil.toOsgiVersion(((version ?: project.platform.featureVersion) ?: project.version) ?: '1.0.0').toString() this.providerName = providerName ?: project.platform.featureProvider this.id = id this.label = label ?: id this.license = license ?: "" - + this.description = description ?: "" + this.copyright = copyright ?: "" + // create masking delegate to be able to intercept internal call results Closure maskedConfig = null CustomConfigDelegate maskingDelegate = null @@ -104,43 +117,43 @@ class ArtifactFeature implements Feature { configClone.resolveStrategy = Closure.DELEGATE_FIRST configClone() } - + maskedConfig.delegate = project.platform // delegate is the platform extension maskedConfig() - + // save feature configuration project.platform.features[this.id] = this } - - @Override + + @Override public String getVersion() { if (!finalVersion) { finalVersion = VersionUtil.addQualifier(version, this, project) } finalVersion } - + Iterable getBundles() { /* * Attention: a call to this method can only yield a sensible * result after the artifacts map has been populated by the * respective Gradle task. */ - + // collect all artifacts that match the respective condition def artifacts = project.platform.artifacts.values().findAll { BundleArtifact artifact -> configArtifacts.any { ArtifactsMatch match -> match.acceptArtifact(artifact) } } - + // collect transitive dependencies transitiveArtifacts(artifacts) } - + private Iterable transitiveArtifacts(Collection artifacts) { Map allArtifacts = [:] - + artifacts.each { BundleArtifact artifact -> if (artifact instanceof DependencyArtifact) { artifact.representedDependencies.each { ResolvedDependency dep -> @@ -151,24 +164,24 @@ class ArtifactFeature implements Feature { } } } - + // in any case, add the bundle itself allArtifacts[artifact.id] = artifact } - - + + // artifact bundles allArtifacts.values().findAll { BundleArtifact ba -> !ba.isSource() } - } - + } + /** * Find bundle artifact representations for resolved artifacts. */ private Collection findArtifacts(Iterable arts) { def result = [] - + arts.each { ResolvedArtifact ra -> def id = "${ra.moduleVersion.id.group}:${ra.moduleVersion.id.name}:${ra.moduleVersion.id.version}" def ba = project.platform.artifacts[id] @@ -176,17 +189,17 @@ class ArtifactFeature implements Feature { result << ba } } - + result } - + Iterable getIncludedFeatures() { // resolve feature IDs configFeatures.collect { project.platform.features[it] }.findAll() } - + /** * Delegate for the configuration closure to intercept calls * for the feature configuration. @@ -201,7 +214,17 @@ class ArtifactFeature implements Feature { @Override def invokeMethod(String name, def args) { - //TODO support manually adding a feature reference + if (name == "requires") { + def requiredNotation = args[0] + if (requiredNotation instanceof Map) { + def featureName = requiredNotation.featureName + def version = requiredNotation.version + def match = requiredNotation.match + def required = new RequiredFeature(featureName, version, match) + feature.requiredFeatures.add(required) + } + return + } /* * If there are further nested closures inside features @@ -209,15 +232,15 @@ class ArtifactFeature implements Feature { * is asked first, and we cannot intercept the call. * Thus as an alternative, 'plugin' can (should) be called instead * of 'bundle' inside feature. - * + * * XXX an alternative would be having some kind of feature stack in * the extension, but this requires then the bundle method in the - * extension to add the bundle to the feature. + * extension to add the bundle to the feature. */ if (name == 'plugin') name = 'bundle' - + def result = orgDelegate."$name"(*args) - + // intercept result if (result instanceof ArtifactsMatch) { // bundle or merge @@ -226,10 +249,10 @@ class ArtifactFeature implements Feature { if (result instanceof Feature) { feature.configFeatures << feature.id } - + result } - + @Override def getProperty(String name) { if (name == 'includes') { @@ -240,11 +263,11 @@ class ArtifactFeature implements Feature { orgDelegate."$name" } } - + @Override void setProperty(String name, def value) { orgDelegate."$name" = value } } - + } diff --git a/src/main/groovy/org/standardout/gradle/plugin/platform/internal/util/FeatureUtil.groovy b/src/main/groovy/org/standardout/gradle/plugin/platform/internal/util/FeatureUtil.groovy index 368cbdc..548731f 100644 --- a/src/main/groovy/org/standardout/gradle/plugin/platform/internal/util/FeatureUtil.groovy +++ b/src/main/groovy/org/standardout/gradle/plugin/platform/internal/util/FeatureUtil.groovy @@ -52,6 +52,27 @@ class FeatureUtil { if (feature.license) { license(feature.license) } + + if (feature.description) { + description(feature.description) + } + + if (feature.copyright) { + copyright(feature.copyright) + } + + if (!feature.requiredFeatures.isEmpty()) { + requires() { + //required features + for (Feature.RequiredFeature required : feature.requiredFeatures.sort(true, { it.featureName })) { + def version = required.version?:'0.0.0' + def match = required.match?:"greaterOrEqual" + xml.import(feature: required.featureName, version: version, match:match) + } + } + } + + // included features for (Feature included : feature.includedFeatures.sort(true, { it.id })) { def version = included.version?:'0.0.0'