Skip to content

Commit

Permalink
feat: support configuring feature description, copyright and required…
Browse files Browse the repository at this point in the history
… features
  • Loading branch information
robserm authored Nov 27, 2024
1 parent 5f93c8f commit ed3f0a7
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 42 deletions.
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ class DefaultFeature implements Feature {
String version
String providerName
String license
String description
String copyright
List<BundleArtifact> bundles = []
List<Feature> includedFeatures = []
Project project
Expand Down Expand Up @@ -59,4 +61,10 @@ class DefaultFeature implements Feature {
includedFeatures == null ? [] : includedFeatures
}

@Override
public Iterable<RequiredFeature> getRequiredFeatures()
{
[]
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,27 @@ public interface Feature {
public String getProviderName();

public String getLicense();


public String getDescription();

public String getCopyright();

public Iterable<BundleArtifact> getBundles();

public Iterable<Feature> getIncludedFeatures();


public Iterable<RequiredFeature> 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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,53 +31,64 @@ 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<ArtifactsMatch> configArtifacts = []

/**
* List of included features IDs
*/
final List<String> configFeatures = []


/**
* List of required features
*/
List<RequiredFeature> 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
label = featureNotation.name
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
Expand All @@ -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
Expand All @@ -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<BundleArtifact> 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<BundleArtifact> transitiveArtifacts(Collection<BundleArtifact> artifacts) {
Map<String, BundleArtifact> allArtifacts = [:]

artifacts.each { BundleArtifact artifact ->
if (artifact instanceof DependencyArtifact) {
artifact.representedDependencies.each { ResolvedDependency dep ->
Expand All @@ -151,42 +164,42 @@ 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<BundleArtifact> findArtifacts(Iterable<ResolvedArtifact> 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]
if (ba != null) {
result << ba
}
}

result
}

Iterable<Feature> getIncludedFeatures() {
// resolve feature IDs
configFeatures.collect {
project.platform.features[it]
}.findAll()
}

/**
* Delegate for the configuration closure to intercept calls
* for the feature configuration.
Expand All @@ -201,23 +214,33 @@ 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
* that have OWNER_FIRST resolve strategy, the PlatformPluginExtension
* 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
Expand All @@ -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') {
Expand All @@ -240,11 +263,11 @@ class ArtifactFeature implements Feature {
orgDelegate."$name"
}
}

@Override
void setProperty(String name, def value) {
orgDelegate."$name" = value
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down

0 comments on commit ed3f0a7

Please sign in to comment.