Skip to content

Commit 7b62479

Browse files
committed
Generate plugin spec
1 parent 2e49fa7 commit 7b62479

File tree

5 files changed

+119
-31
lines changed

5 files changed

+119
-31
lines changed

Makefile

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
clean:
2-
./gradlew clean
31

42
assemble:
53
./gradlew assemble
4+
5+
install:
6+
./gradlew publishToMavenLocal
7+
8+
clean:
9+
./gradlew clean
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package io.nextflow.gradle
2+
3+
import org.gradle.api.file.RegularFileProperty
4+
import org.gradle.api.provider.ListProperty
5+
import org.gradle.api.tasks.Input
6+
import org.gradle.api.tasks.JavaExec
7+
import org.gradle.api.tasks.OutputFile
8+
9+
/**
10+
* Gradle task to generate the plugin spec file.
11+
*/
12+
class BuildSpecTask extends JavaExec {
13+
14+
@Input
15+
final ListProperty<String> extensionPoints
16+
17+
@OutputFile
18+
final RegularFileProperty specFile
19+
20+
BuildSpecTask() {
21+
extensionPoints = project.objects.listProperty(String)
22+
extensionPoints.convention(project.provider {
23+
project.extensions.getByType(NextflowPluginConfig).extensionPoints
24+
})
25+
26+
specFile = project.objects.fileProperty()
27+
specFile.convention(project.layout.buildDirectory.file("resources/main/META-INF/spec.json"))
28+
29+
getMainClass().set('nextflow.plugin.spec.PluginSpecWriter')
30+
31+
project.afterEvaluate {
32+
setClasspath(project.sourceSets.getByName('specFile').runtimeClasspath)
33+
setArgs([specFile.get().asFile.toString()] + extensionPoints.get())
34+
}
35+
36+
doFirst {
37+
specFile.get().asFile.parentFile.mkdirs()
38+
}
39+
}
40+
}

src/main/groovy/io/nextflow/gradle/NextflowPlugin.groovy

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ import org.gradle.jvm.toolchain.JavaLanguageVersion
1414
* A gradle plugin for nextflow plugin projects.
1515
*/
1616
class NextflowPlugin implements Plugin<Project> {
17+
1718
private static final int JAVA_TOOLCHAIN_VERSION = 21
19+
1820
private static final int JAVA_VERSION = 17
1921

2022
@Override
@@ -58,6 +60,11 @@ class NextflowPlugin implements Plugin<Project> {
5860
reps.maven { url = "https://s3-eu-west-1.amazonaws.com/maven.seqera.io/releases" }
5961
}
6062

63+
project.configurations {
64+
specFile
65+
specFileImplementation.extendsFrom(specFile)
66+
}
67+
6168
project.afterEvaluate {
6269
config.validate()
6370
final nextflowVersion = config.nextflowVersion
@@ -86,11 +93,19 @@ class NextflowPlugin implements Plugin<Project> {
8693
deps.testRuntimeOnly "net.bytebuddy:byte-buddy:1.14.17"
8794
deps.testImplementation(testFixtures("io.nextflow:nextflow:${nextflowVersion}"))
8895
deps.testImplementation(testFixtures("io.nextflow:nf-commons:${nextflowVersion}"))
96+
97+
// dependencies for buildSpec task
98+
deps.specFile "io.nextflow:nextflow:${nextflowVersion}"
99+
deps.specFile project.files(project.tasks.jar.archiveFile)
89100
}
90101
}
102+
91103
// use JUnit 5 platform
92104
project.test.useJUnitPlatform()
93105

106+
// sometimes tests depend on the assembled plugin
107+
project.tasks.test.dependsOn << project.tasks.assemble
108+
94109
// -----------------------------------
95110
// Add plugin details to jar manifest
96111
// -----------------------------------
@@ -107,25 +122,37 @@ class NextflowPlugin implements Plugin<Project> {
107122
project.tasks.jar.dependsOn << project.tasks.extensionPoints
108123
project.tasks.compileTestGroovy.dependsOn << project.tasks.extensionPoints
109124

125+
// buildSpec - generates the plugin spec file
126+
project.sourceSets.create('specFile') { sourceSet ->
127+
sourceSet.compileClasspath += project.configurations.getByName('specFile')
128+
sourceSet.runtimeClasspath += project.configurations.getByName('specFile')
129+
}
130+
project.tasks.register('buildSpec', BuildSpecTask)
131+
project.tasks.buildSpec.dependsOn << [
132+
project.tasks.jar,
133+
project.tasks.compileSpecFileGroovy
134+
]
135+
110136
// packagePlugin - builds the zip file
111137
project.tasks.register('packagePlugin', PluginPackageTask)
112138
project.tasks.packagePlugin.dependsOn << [
113139
project.tasks.extensionPoints,
114-
project.tasks.classes
140+
project.tasks.classes,
141+
project.tasks.buildSpec
115142
]
116143
project.tasks.assemble.dependsOn << project.tasks.packagePlugin
117144

118145
// installPlugin - installs plugin to (local) nextflow plugins dir
119146
project.tasks.register('installPlugin', PluginInstallTask)
120147
project.tasks.installPlugin.dependsOn << project.tasks.assemble
121148

122-
// sometimes tests depend on the assembled plugin
123-
project.tasks.test.dependsOn << project.tasks.assemble
124-
149+
// releasePlugin - publish plugin release to registry
125150
project.afterEvaluate {
126151
// Always create registry release task - it will use fallback configuration if needed
127152
project.tasks.register('releasePluginToRegistry', RegistryReleaseTask)
128-
project.tasks.releasePluginToRegistry.dependsOn << project.tasks.packagePlugin
153+
project.tasks.releasePluginToRegistry.dependsOn << [
154+
project.tasks.packagePlugin
155+
]
129156

130157
// Always create the main release task
131158
project.tasks.register('releasePlugin', {

src/main/groovy/io/nextflow/gradle/registry/RegistryClient.groovy

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,17 @@ class RegistryClient {
4747
*
4848
* @param id The plugin identifier/name
4949
* @param version The plugin version (must be valid semver)
50-
* @param file The plugin zip file to upload
50+
* @param spec The plugin spec JSON file
51+
* @param archive The plugin zip file to upload
5152
* @throws RegistryReleaseException if the upload fails or returns an error
5253
*/
53-
def release(String id, String version, File file) {
54+
def release(String id, String version, File spec, File archive) {
5455
def client = HttpClient.newBuilder()
5556
.connectTimeout(Duration.ofSeconds(30))
5657
.build()
5758

5859
def boundary = "----FormBoundary" + UUID.randomUUID().toString().replace("-", "")
59-
def multipartBody = buildMultipartBody(id, version, file, boundary)
60+
def multipartBody = buildMultipartBody(id, version, spec, archive, boundary)
6061

6162
def requestUri = url.resolve("v1/plugins/release")
6263
def request = HttpRequest.newBuilder()
@@ -92,50 +93,58 @@ class RegistryClient {
9293
return message.toString()
9394
}
9495

95-
private byte[] buildMultipartBody(String id, String version, File file, String boundary) {
96+
private byte[] buildMultipartBody(String id, String version, File spec, File archive, String boundary) {
9697
def output = new ByteArrayOutputStream()
9798
def writer = new PrintWriter(new OutputStreamWriter(output, "UTF-8"), true)
9899
def lineEnd = "\r\n"
99-
100+
100101
// Calculate SHA-512 checksum
101-
def fileBytes = Files.readAllBytes(file.toPath())
102-
def checksum = computeSha512(fileBytes)
103-
102+
def archiveBytes = Files.readAllBytes(archive.toPath())
103+
def checksum = computeSha512(archiveBytes)
104+
104105
// Add id field
105106
writer.append("--${boundary}").append(lineEnd)
106107
writer.append("Content-Disposition: form-data; name=\"id\"").append(lineEnd)
107108
writer.append("Content-Type: text/plain; charset=UTF-8").append(lineEnd)
108109
writer.append(lineEnd)
109110
writer.append(id).append(lineEnd)
110-
111+
111112
// Add version field
112113
writer.append("--${boundary}").append(lineEnd)
113114
writer.append("Content-Disposition: form-data; name=\"version\"").append(lineEnd)
114115
writer.append("Content-Type: text/plain; charset=UTF-8").append(lineEnd)
115116
writer.append(lineEnd)
116117
writer.append(version).append(lineEnd)
117-
118+
118119
// Add checksum field
119120
writer.append("--${boundary}").append(lineEnd)
120121
writer.append("Content-Disposition: form-data; name=\"checksum\"").append(lineEnd)
121122
writer.append("Content-Type: text/plain; charset=UTF-8").append(lineEnd)
122123
writer.append(lineEnd)
123124
writer.append("sha512:${checksum}").append(lineEnd)
124-
125-
// Add file field
125+
126+
// Add spec field
127+
writer.append("--${boundary}").append(lineEnd)
128+
writer.append("Content-Disposition: form-data; name=\"spec\"").append(lineEnd)
129+
writer.append("Content-Type: application/json").append(lineEnd)
130+
writer.append(lineEnd)
131+
writer.append(spec.text).append(lineEnd)
132+
writer.append(lineEnd)
133+
134+
// Add archive field
126135
writer.append("--${boundary}").append(lineEnd)
127-
writer.append("Content-Disposition: form-data; name=\"artifact\"; filename=\"${file.name}\"").append(lineEnd)
136+
writer.append("Content-Disposition: form-data; name=\"artifact\"; filename=\"${archive.name}\"").append(lineEnd)
128137
writer.append("Content-Type: application/zip").append(lineEnd)
129138
writer.append(lineEnd)
130139
writer.flush()
131-
132-
// Write file bytes (already read above for checksum)
133-
output.write(fileBytes)
134-
140+
141+
// Write archive bytes (already read above for checksum)
142+
output.write(archiveBytes)
143+
135144
writer.append(lineEnd)
136145
writer.append("--${boundary}--").append(lineEnd)
137146
writer.close()
138-
147+
139148
return output.toByteArray()
140149
}
141150

src/main/groovy/io/nextflow/gradle/registry/RegistryReleaseTask.groovy

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import org.gradle.api.tasks.TaskAction
99

1010
/**
1111
* Gradle task for releasing a Nextflow plugin to a registry.
12-
*
12+
*
1313
* This task uploads the assembled plugin zip file to a configured registry
1414
* using the registry's REST API. The plugin zip file is sent as a multipart
1515
* form request along with the plugin ID and version metadata.
@@ -23,6 +23,9 @@ class RegistryReleaseTask extends DefaultTask {
2323
@InputFile
2424
final RegularFileProperty zipFile
2525

26+
@InputFile
27+
final RegularFileProperty specFile
28+
2629
RegistryReleaseTask() {
2730
group = 'Nextflow Plugin'
2831
description = 'Release the assembled plugin to the registry'
@@ -32,21 +35,26 @@ class RegistryReleaseTask extends DefaultTask {
3235
zipFile.convention(project.provider {
3336
buildDir.file("distributions/${project.name}-${project.version}.zip")
3437
})
38+
39+
specFile = project.objects.fileProperty()
40+
specFile.convention(project.provider {
41+
buildDir.file("resources/main/META-INF/spec.json")
42+
})
3543
}
3644

3745
/**
3846
* Executes the registry release task.
39-
*
47+
*
4048
* This method retrieves the plugin configuration and creates a RegistryClient
4149
* to upload the plugin zip file to the configured registry endpoint.
42-
*
50+
*
4351
* @throws RegistryReleaseException if the upload fails
4452
*/
4553
@TaskAction
4654
def run() {
4755
final version = project.version.toString()
4856
final plugin = project.extensions.getByType(NextflowPluginConfig)
49-
57+
5058
// Get or create registry configuration
5159
def registryConfig
5260
if (plugin.registry) {
@@ -58,8 +66,8 @@ class RegistryReleaseTask extends DefaultTask {
5866

5967
def registryUri = new URI(registryConfig.resolvedUrl)
6068
def client = new RegistryClient(registryUri, registryConfig.resolvedAuthToken)
61-
client.release(project.name, version, project.file(zipFile))
62-
69+
client.release(project.name, version, project.file(specFile), project.file(zipFile))
70+
6371
// Celebrate successful plugin upload! 🎉
6472
project.logger.lifecycle("🎉 SUCCESS! Plugin '${project.name}' version ${version} has been successfully released to Nextflow Registry [${registryUri}]!")
6573
}

0 commit comments

Comments
 (0)