Skip to content

Commit ffd72b9

Browse files
committed
feat: support creating feature version files in update site
Creation of these files is intended to be used in combination with Renovate, where these files can serve as a custom datasource for feature versions in a p2 repository created by bnd-platform.
1 parent 4e8e765 commit ffd72b9

File tree

10 files changed

+266
-8
lines changed

10 files changed

+266
-8
lines changed

.github/workflows/check.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
if: always() # always run even if the previous step fails
3333
with:
3434
report_paths: 'build/test-results/**/*.xml'
35-
require_tests: false # currently no tests present
35+
require_tests: true
3636

3737
annotate_only: true
3838
detailed_summary: true

.github/workflows/publish.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ jobs:
4141
# ORG_GRADLE_PROJECT_signing.password: ${{ secrets.SONATYE_PGP_PASSWORD }}
4242
# ORG_GRADLE_PROJECT_signing.keyId: ${{ secrets.SONATYE_PGP_KEY_ID }}
4343
# ORG_GRADLE_PROJECT_signing.secretKeyRingFile: /home/runner/.gnupg/secring.gpg
44-
44+
4545
# in-memory key
4646
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SONATYE_PGP_PASSWORD }}
4747
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SONATYE_PGP_PRIVATE_KEY }}
@@ -55,8 +55,8 @@ jobs:
5555
if: always() # always run even if the previous step fails
5656
with:
5757
report_paths: 'build/test-results/**/*.xml'
58-
require_tests: false # currently no tests present
58+
require_tests: true # currently no tests present
5959

6060
annotate_only: true
6161
detailed_summary: true
62-
# fail_on_failure: true
62+
# fail_on_failure: true

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ bin/
66
.project
77
.settings/
88
*~
9+
/.idea/

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ apply plugin: 'org.standardout.bnd-platform'
6363
The **platform** plugin comes with several Gradle tasks - the following are the main tasks and build upon each other:
6464

6565
* ***bundles*** - create bundles and write them to **build/plugins**
66-
* ***potentialOptionalImports*** Creates a potentialOptionalImports.txt file of imported packages of all generated bundles with the optionalImport instruction (See "Optional Dependencies" section below)
66+
* ***potentialOptionalImports*** Creates a potentialOptionalImports.txt file of imported packages of all generated bundles with the optionalImport instruction (See "Optional Dependencies" section below)
6767
* ***updateSite*** - create a p2 repository from the bundles and write it to **build/updatesite** (default)
6868
* ***updateSiteZip*** - create a ZIP archive from the p2 repository and write it to **build/updatesite.zip** (default)
6969

@@ -462,6 +462,7 @@ Via the platform extension there are several settings you can provide:
462462
* **updateSiteDir** - the directory the generated p2 repository is written to (default: `new File(buildDir, 'updatesite')`)
463463
* **updateSiteZipFile** - the target file for the zipped p2 repository (default: `new File(buildDir, 'updatesite.zip')`)
464464
* **appendUpdateSite** - if any the generated p2 repository should be appended to the one that already exists in **updateSiteDir** (default: `false`)
465+
* **createFeatureVersionFiles** - if for the created update site, a version file should be created per feature, e.g. `<feature-id>_versions.json`, that includes information on the versions of the feature available in the p2 repository (default: `false`)
465466
* **eclipseHome** - File object pointing to the directory of a local Eclipse installation to be used for generating the p2 repository (default: `null`)
466467
* **eclipseMirror** - Eclipse download URLs to be used when no local installation is provided via *eclipseHome*. Since version 3 uses an Eclipse 2023-09 mirror by default.
467468
* **downloadsDir** - the directory to store the downloaded Eclipse installation on local, this works if *eclipseHome* is not specified. (default: `new File(buildDir, 'eclipse-downloads')`)

build.gradle

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@ repositories {
1515
group = 'org.standardout'
1616
version = '3.1.0-SNAPSHOT'
1717

18-
sourceCompatibility = '1.8'
19-
targetCompatibility = '1.8'
18+
java {
19+
toolchain {
20+
languageVersion = JavaLanguageVersion.of(8)
21+
}
22+
}
2023

2124
jar {
2225
// include license into jar
@@ -35,6 +38,16 @@ dependencies {
3538
implementation 'commons-io:commons-io:2.15.1'
3639
implementation 'de.undercouch:gradle-download-task:5.6.0'
3740
implementation localGroovy()
41+
42+
// Testing
43+
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.2'
44+
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.2'
45+
46+
testImplementation("org.assertj:assertj-core:3.25.1")
47+
}
48+
49+
test {
50+
useJUnitPlatform()
3851
}
3952

4053
tasks.wrapper {
@@ -100,7 +113,7 @@ publishing {
100113
pom {
101114
name = 'bnd-platform'
102115
packaging = 'jar'
103-
116+
104117
configurePom(pom)
105118
}
106119
}

src/main/groovy/org/standardout/gradle/plugin/platform/PlatformPlugin.groovy

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.standardout.gradle.plugin.platform
1818

19+
import org.standardout.gradle.plugin.platform.internal.util.VersionFile
20+
1921
import java.util.jar.*
2022

2123
import org.gradle.api.GradleException
@@ -275,6 +277,11 @@ public class PlatformPlugin implements Plugin<Project> {
275277
}
276278

277279
project.logger.info 'Built p2 repository.'
280+
281+
def createFeatureVersionFiles = project.platform.createFeatureVersionFiles
282+
if (createFeatureVersionFiles) {
283+
VersionFile.createFeatureVersionFiles(project.platform.updateSiteDir)
284+
}
278285
}
279286
}
280287

src/main/groovy/org/standardout/gradle/plugin/platform/PlatformPluginExtension.groovy

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,16 @@ class PlatformPluginExtension {
309309
* If the update site should be appended instead of over-written.
310310
*/
311311
boolean appendUpdateSite = false
312+
313+
/**
314+
* If after creating the update site, additional files should be created for
315+
* each feature contained in the update site, that includes the information
316+
* on which versions are contained in the update site.
317+
*
318+
* This can be used with tools like Renovate to automated updates of feature
319+
* versions where the update site is used.
320+
*/
321+
boolean createFeatureVersionFiles = false
312322

313323
/**
314324
* The directory of a local Eclipse installation. If none is specified the
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package org.standardout.gradle.plugin.platform.internal.util
2+
3+
import groovy.json.JsonOutput
4+
import groovy.xml.XmlSlurper
5+
import groovy.xml.slurpersupport.GPathResult
6+
7+
import java.util.zip.ZipFile
8+
9+
class VersionFile {
10+
11+
/**
12+
* Read an XML file from a Jar/Zip file using XMLSlurper.
13+
*
14+
* @param jarFile the Jar file the XML is contained in
15+
* @param xmlFileName the name of the XML file in the Jar file
16+
* @return the parsed XML file or null if the file did not exist
17+
*/
18+
private static GPathResult readXmlFromJar(File jarFile, String xmlFileName) {
19+
def zipFile = new ZipFile(jarFile)
20+
def entry = zipFile.getEntry(xmlFileName)
21+
22+
if (entry) {
23+
zipFile.getInputStream(entry).withStream {
24+
new XmlSlurper().parse(it)
25+
}
26+
} else {
27+
null
28+
}
29+
}
30+
31+
/**
32+
* Create version files for features in a p2 repository.
33+
*
34+
* @param updateSiteDir the location of the p2 repository
35+
*/
36+
static def createFeatureVersionFiles(File updateSiteDir) {
37+
//TODO delete any previously existing version files?
38+
39+
GPathResult artifactsXml
40+
41+
def artifactsJar = new File(updateSiteDir, 'artifacts.jar')
42+
if (artifactsJar.exists()) {
43+
artifactsXml = readXmlFromJar(artifactsJar, 'artifacts.xml')
44+
}
45+
46+
if (artifactsXml == null) {
47+
def artifactsXmlFile = new File(updateSiteDir, 'artifacts.xml')
48+
artifactsXml = new XmlSlurper().parse(artifactsXmlFile)
49+
}
50+
51+
if (artifactsXml != null) {
52+
createFeatureVersionFiles(updateSiteDir, artifactsXml)
53+
}
54+
}
55+
56+
static def createFeatureVersionFiles(File updateSiteDir, GPathResult artifactsXml) {
57+
def featureVersions = collectFeatureVersions(artifactsXml)
58+
59+
featureVersions.forEach { featureId, versions ->
60+
def versionFile = new File(updateSiteDir, "${featureId}_version.json")
61+
writeVersionFile(versionFile, versions)
62+
}
63+
}
64+
65+
static Map<String, List<String>> collectFeatureVersions(GPathResult artifactsXml) {
66+
def features = artifactsXml.artifacts.artifact.findAll{ a -> a.@classifier == 'org.eclipse.update.feature' }
67+
68+
features
69+
.findResults { feature ->
70+
def id = feature.@id as String
71+
def version = feature.@version as String
72+
if (id && version) {
73+
[id: id, version: version]
74+
}
75+
else {
76+
null
77+
}
78+
}
79+
.groupBy { it.id }
80+
.collectEntries { id, objects ->
81+
[id, objects.collect { obj -> obj.version }]
82+
}
83+
}
84+
85+
/**
86+
* Write a version file with the given versions.
87+
* The format used is a Json file that is compatible with custom datasources in Renovate.
88+
* See https://docs.renovatebot.com/modules/datasource/custom/#usage
89+
*
90+
* @param versionFile the file to write
91+
* @param versions the versions
92+
*/
93+
static void writeVersionFile(File versionFile, List<String> versions) {
94+
versionFile.text = JsonOutput.toJson([releases: versions.collect { [version: it] }])
95+
}
96+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package org.standardout.gradle.plugin.platform.util
2+
3+
import groovy.xml.XmlSlurper
4+
import org.junit.jupiter.api.Test
5+
import org.standardout.gradle.plugin.platform.internal.util.VersionFile
6+
7+
import static org.assertj.core.api.Assertions.*
8+
9+
class VersionFileTest {
10+
11+
@Test
12+
void testCollectFeatureVersions() {
13+
def xml = getClass().getClassLoader().getResourceAsStream('artifacts-xml/example.xml').withStream {
14+
new XmlSlurper().parse(it)
15+
}
16+
17+
def featureName = 'to.wetransform.offlineresources.feature'
18+
19+
def versions = VersionFile.collectFeatureVersions(xml)
20+
21+
assertThat(versions)
22+
.as('should have exactly one feature')
23+
.hasSize(1)
24+
.containsOnlyKeys(featureName)
25+
.extractingByKey(featureName)
26+
.asList()
27+
.hasSize(2) // 2 versions expected
28+
.containsExactly('2024.3.15.bnd-Ia6g4Q', '2024.3.18.bnd-tNmhEg')
29+
}
30+
31+
@Test
32+
void testCollectFeatureVersionsEmpty() {
33+
def xml = new XmlSlurper().parseText('<repository />')
34+
35+
def versions = VersionFile.collectFeatureVersions(xml)
36+
37+
assertThat(versions)
38+
.isEmpty()
39+
}
40+
41+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?xml version='1.0' encoding='UTF-8'?>
2+
<?artifactRepository version='1.1.0'?>
3+
<repository name='file:/home/simon/repos/offline-resources/updatesite/build/updatesite/ - artifacts' type='org.eclipse.equinox.p2.artifact.repository.simpleRepository' version='1.0.0'>
4+
<properties size='2'>
5+
<property name='p2.timestamp' value='1710770425928'/>
6+
<property name='p2.compressed' value='true'/>
7+
</properties>
8+
<mappings size='3'>
9+
<rule filter='(&amp; (classifier=osgi.bundle))' output='${repoUrl}/plugins/${id}_${version}.jar'/>
10+
<rule filter='(&amp; (classifier=binary))' output='${repoUrl}/binary/${id}_${version}'/>
11+
<rule filter='(&amp; (classifier=org.eclipse.update.feature))' output='${repoUrl}/features/${id}_${version}.jar'/>
12+
</mappings>
13+
<artifacts size='9'>
14+
<artifact classifier='org.eclipse.update.feature' id='to.wetransform.offlineresources.feature' version='2024.3.15.bnd-Ia6g4Q'>
15+
<properties size='5'>
16+
<property name='artifact.size' value='441'/>
17+
<property name='download.size' value='441'/>
18+
<property name='download.checksum.sha-512' value='5f1e413265b4d7bcffc75514104764c66aad18f825d97d71daca07eb874ab0c41e1f762074d91ff2db49450522effb320aee5fad6afd4ff702db48aed032df1a'/>
19+
<property name='download.checksum.sha-256' value='9ab6b0ef619a436d01ddb1d09d69f70445882a569311d62d267d052f9066d1d5'/>
20+
<property name='download.contentType' value='application/zip'/>
21+
</properties>
22+
</artifact>
23+
<artifact classifier='osgi.bundle' id='to.wetransform.offline-resources.www.w3.org' version='2024.1.25.bnd-8IYMsg'>
24+
<properties size='4'>
25+
<property name='artifact.size' value='18532'/>
26+
<property name='download.size' value='18532'/>
27+
<property name='download.checksum.sha-512' value='eeece45a645768df0797a0e990ec8c34b6c3ea9caf10def4474288aead3c9e44a3e1b035be25f9757b52f9b0cd4feea02c8afeca9f9264b357f146ae9ca7ae6e'/>
28+
<property name='download.checksum.sha-256' value='769c333211055e1e184510d2e95b4bcf9b1dd1e88303b023374880bcb1b96fe2'/>
29+
</properties>
30+
</artifact>
31+
<artifact classifier='osgi.bundle' id='to.wetransform.offline-resources.schemas.opengis.net' version='2024.3.18.bnd-8IYMsg'>
32+
<properties size='4'>
33+
<property name='artifact.size' value='14571884'/>
34+
<property name='download.size' value='14571884'/>
35+
<property name='download.checksum.sha-512' value='bb9e1af2658ce727d7e6bd107e28547514fbfe7ac8f23e11a79f25ca1a42a5b935aaaf8720eec7d19dbdf3b9416e20ff09cf7738151ec8b6d6581aea1c1cf4e6'/>
36+
<property name='download.checksum.sha-256' value='33340359c5c68ee62977104005290aeed5cc689d4ab13dcf329dc5d859003f14'/>
37+
</properties>
38+
</artifact>
39+
<artifact classifier='org.eclipse.update.feature' id='to.wetransform.offlineresources.feature' version='2024.3.18.bnd-tNmhEg'>
40+
<properties size='5'>
41+
<property name='artifact.size' value='443'/>
42+
<property name='download.size' value='443'/>
43+
<property name='download.checksum.sha-512' value='36e12880f05a1c4c0d10170b8a84366463fc96710eea29ce9128f0c70154624ac8c38f51a11a40898e31c274da14baea0ccde06360c308444c1c1461d05a05df'/>
44+
<property name='download.checksum.sha-256' value='de43b6f342cf3710a26d4a1d741c9bb7385677e8e74e95ba0b4c067b1573fe1d'/>
45+
<property name='download.contentType' value='application/zip'/>
46+
</properties>
47+
</artifact>
48+
<artifact classifier='osgi.bundle' id='to.wetransform.offline-resources.inspire.ec.europa.eu' version='2024.3.15.bnd-8IYMsg'>
49+
<properties size='4'>
50+
<property name='artifact.size' value='5839548'/>
51+
<property name='download.size' value='5839548'/>
52+
<property name='download.checksum.sha-512' value='4d8f0972873dcc2242eb5b174d01a7ba70a6df732fa0bf6aa0636caaae3a4be9a126484c751c2fd5300bf25a1d30cc438aedd1f188ec7fb88d2dcb394539cfde'/>
53+
<property name='download.checksum.sha-256' value='afffa6b7e58de085d0d6f1eb795ad5db0ed6b5b99c221888eebc5d2a36c2319e'/>
54+
</properties>
55+
</artifact>
56+
<artifact classifier='osgi.bundle' id='to.wetransform.offline-resources.schemas.opengis.net' version='2024.3.8.bnd-8IYMsg'>
57+
<properties size='4'>
58+
<property name='artifact.size' value='14571887'/>
59+
<property name='download.size' value='14571887'/>
60+
<property name='download.checksum.sha-512' value='233eebc16468cc3d23dc5783459bee5593de206dff936cc1afeb45ecc806c50b8df922543ab265e22fcffb715ce3a1f0e2e937e61dace5ea3ef553caba8d9378'/>
61+
<property name='download.checksum.sha-256' value='4471f1e354c629fe6f828374a05240fe45fb5fd4b61ac157bd94be5f4c7cc12d'/>
62+
</properties>
63+
</artifact>
64+
<artifact classifier='osgi.bundle' id='to.wetransform.offline-resources.repository.gdi-de.org' version='2024.2.20.bnd-8IYMsg'>
65+
<properties size='4'>
66+
<property name='artifact.size' value='4760565'/>
67+
<property name='download.size' value='4760565'/>
68+
<property name='download.checksum.sha-512' value='c36f96ebac58d758ab3ed1e409c71c84f9dee7c7410c435de2110a895d32a9df7b038696b60b6b4132e6f0ac3a044745c1fb983ddaf469228acdd4a42866d9a0'/>
69+
<property name='download.checksum.sha-256' value='8a0c058d6ef95a9737f68f57bb7bb152fe4b3a8f5253deec7749332c14da87ef'/>
70+
</properties>
71+
</artifact>
72+
<artifact classifier='osgi.bundle' id='to.wetransform.offline-resources.schemas.geosciml.org' version='2024.1.25.bnd-8IYMsg'>
73+
<properties size='4'>
74+
<property name='artifact.size' value='4029'/>
75+
<property name='download.size' value='4029'/>
76+
<property name='download.checksum.sha-512' value='3f9e1a4f76ad2a7c9ab6de03c6b50629ee61b59fbdee1021b98bc01732838b48dd82b2c49a41144ef7abfac573732adeb0f8372f54cfd067db0395161383647e'/>
77+
<property name='download.checksum.sha-256' value='1ad370890bc7e71621a5f9f7773d7cce401f30db2dcd3c8a66d3fe9b199a1e6c'/>
78+
</properties>
79+
</artifact>
80+
<artifact classifier='osgi.bundle' id='to.wetransform.offline-resources.portele.de' version='2024.1.25.bnd-8IYMsg'>
81+
<properties size='4'>
82+
<property name='artifact.size' value='1939'/>
83+
<property name='download.size' value='1939'/>
84+
<property name='download.checksum.sha-512' value='46b083abca27a1273245422148cb1c6708b365d706633ab9170116c50b42599fcd6a4953bec3ef4fa7dd90b472b41f8e25d4e366b805c020e44d424e1704a606'/>
85+
<property name='download.checksum.sha-256' value='67707d04caaad5ed713c96e9f2182e856d4bf3a4f7b69ff2ad714a3e0170212b'/>
86+
</properties>
87+
</artifact>
88+
</artifacts>
89+
</repository>

0 commit comments

Comments
 (0)