Skip to content

Commit ca376bf

Browse files
committed
Merge pull request #430 from CoreMedia/check-plugin-version
added @RequiresPlugin annotation to enforce plugin installation
2 parents 3871054 + 32c7bb4 commit ca376bf

27 files changed

+212
-48
lines changed

CONTRIBUTING.md

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,15 @@ job {
5656
```
5757

5858
* Use private or protected access modifiers for context and helper methods that should not be exposed to DSL users.
59+
* Add the `@RequiresPlugin` annotation if the feature needs a specific plugin to be installed in Jenkins.
60+
61+
```groovy
62+
@RequiresPlugin(id = 'foo', minimumVersion = '1.2')
63+
void foo(String fooOption) {
64+
// your implementation goes here
65+
}
66+
```
67+
5968
* Use enum values where appropriate, e.g. when the UI displays a chooser. The enum should be an inner class of the
6069
context which uses the enum. Use conventions for constants for naming enum values. Add the enum to the implicit imports
6170
in `DslScriptLoader.createCompilerConfiguration`.
@@ -64,7 +73,7 @@ in `DslScriptLoader.createCompilerConfiguration`.
6473
class FooContext {
6574
FooOptions option = FooOptions.FIRST
6675
67-
def option(FooOptions option) {
76+
void option(FooOptions option) {
6877
this.option = option
6978
}
7079
@@ -82,7 +91,7 @@ of `enableSomeOption(true)`.
8291
class FooContext {
8392
boolean foo
8493
85-
def foo(boolean foo = true) {
94+
void foo(boolean foo = true) {
8695
this.foo = foo
8796
}
8897
}
@@ -95,19 +104,19 @@ class FooContext {
95104
Map<String, String> jvmOptions = [:]
96105
List<String> args = []
97106
98-
def jvmOption(String key, String value) {
107+
void jvmOption(String key, String value) {
99108
jvmOptions[key] = value
100109
}
101110
102-
def jvmOptions(Map<String, String> options) {
111+
void jvmOptions(Map<String, String> options) {
103112
jvmOptions.putAll(options)
104113
}
105114
106-
def arg(String arg) {
115+
void arg(String arg) {
107116
args << arg
108117
}
109118
110-
def args(String... args) {
119+
void args(String... args) {
111120
this.args.addAll(args)
112121
}
113122
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package javaposse.jobdsl.dsl.transform
2+
3+
import org.codehaus.groovy.ast.ASTNode
4+
import org.codehaus.groovy.ast.AnnotationNode
5+
import org.codehaus.groovy.ast.ClassHelper
6+
import org.codehaus.groovy.ast.ClassNode
7+
import org.codehaus.groovy.ast.MethodNode
8+
import org.codehaus.groovy.ast.expr.ArgumentListExpression
9+
import org.codehaus.groovy.ast.expr.ConstantExpression
10+
import org.codehaus.groovy.ast.expr.MethodCallExpression
11+
import org.codehaus.groovy.ast.expr.VariableExpression
12+
import org.codehaus.groovy.ast.stmt.BlockStatement
13+
import org.codehaus.groovy.ast.stmt.ExpressionStatement
14+
import org.codehaus.groovy.control.CompilePhase
15+
import org.codehaus.groovy.control.SourceUnit
16+
import org.codehaus.groovy.syntax.Token
17+
import org.codehaus.groovy.transform.ASTTransformation
18+
import org.codehaus.groovy.transform.GroovyASTTransformation
19+
20+
/**
21+
* Global AST transformation for checking plugin requirements.
22+
*
23+
* Each method annotated by <code>@javaposse.jobdsl.dsl.RequiresPlugin</code> is supplemented with a method call to
24+
* <code>jobManagement.requireMinimumPluginVersion()</code> or <code>jobManagement.requirePlugin()</code>. The method
25+
* must have access to a <code>jobManagement</code> field.
26+
*/
27+
@GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS)
28+
class PluginASTTransformation implements ASTTransformation {
29+
private static final ClassNode REQUIRES_PLUGIN_ANNOTATION = ClassHelper.make('javaposse.jobdsl.dsl.RequiresPlugin')
30+
31+
@Override
32+
void visit(ASTNode[] nodes, SourceUnit sourceUnit) {
33+
sourceUnit.AST?.classes*.methods.flatten().each { MethodNode method ->
34+
method.getAnnotations(REQUIRES_PLUGIN_ANNOTATION).each { AnnotationNode requiresPluginAnnotation ->
35+
if (!method.declaringClass.getField('jobManagement')) {
36+
sourceUnit.errorCollector.addError(
37+
"no jobManagement field in $method.declaringClass",
38+
Token.newString(
39+
requiresPluginAnnotation.text,
40+
requiresPluginAnnotation.lineNumber,
41+
requiresPluginAnnotation.columnNumber
42+
),
43+
sourceUnit
44+
)
45+
}
46+
47+
MethodCallExpression pluginCheckStatement
48+
if (requiresPluginAnnotation.members.minimumVersion) {
49+
pluginCheckStatement = new MethodCallExpression(
50+
new VariableExpression('jobManagement'),
51+
new ConstantExpression('requireMinimumPluginVersion'),
52+
new ArgumentListExpression(
53+
requiresPluginAnnotation.members.id,
54+
requiresPluginAnnotation.members.minimumVersion,
55+
)
56+
)
57+
} else {
58+
pluginCheckStatement = new MethodCallExpression(
59+
new VariableExpression('jobManagement'),
60+
new ConstantExpression('requirePlugin'),
61+
new ArgumentListExpression(
62+
requiresPluginAnnotation.members.id,
63+
)
64+
)
65+
}
66+
67+
((BlockStatement) method.code).statements.add(0, new ExpressionStatement(pluginCheckStatement))
68+
}
69+
}
70+
}
71+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
javaposse.jobdsl.dsl.transform.ContextASTTransformation
2+
javaposse.jobdsl.dsl.transform.PluginASTTransformation

job-dsl-core/src/main/groovy/javaposse/jobdsl/dsl/FileJobManagement.groovy

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ class FileJobManagement extends AbstractJobManagement {
7474
new File(root, filePath).text
7575
}
7676

77+
@Override
78+
void requirePlugin(String pluginShortName) {
79+
}
80+
7781
@Override
7882
void requireMinimumPluginVersion(String pluginShortName, String version) {
7983
}

job-dsl-core/src/main/groovy/javaposse/jobdsl/dsl/JobManagement.groovy

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,12 @@ interface JobManagement {
102102
*/
103103
void logDeprecationWarning(String subject, String scriptName, int lineNumber)
104104

105-
/**
105+
/**
106+
* Logs a warning and sets the build status to unstable if given plugin is not installed.
107+
*/
108+
void requirePlugin(String pluginShortName)
109+
110+
/**
106111
* Logs a warning and sets the build status to unstable if the installed version of the given plugin is older than
107112
* the given version.
108113
*/

job-dsl-core/src/main/groovy/javaposse/jobdsl/dsl/MemoryJobManagement.groovy

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ class MemoryJobManagement extends AbstractJobManagement {
7777
body
7878
}
7979

80+
@Override
81+
void requirePlugin(String pluginShortName) {
82+
}
83+
8084
@Override
8185
void requireMinimumPluginVersion(String pluginShortName, String version) {
8286
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package javaposse.jobdsl.dsl
2+
3+
import java.lang.annotation.Documented
4+
import java.lang.annotation.ElementType
5+
import java.lang.annotation.Target
6+
7+
/**
8+
* Indicates that a plugin must be installed to use the features provided by the annotated DSL method. A minimum
9+
* version can be specified as a lower bound for the version of the required plugin.
10+
*/
11+
@Target([ElementType.METHOD])
12+
@Documented
13+
@interface RequiresPlugin {
14+
/**
15+
* The Plugin ID or short name of the required plugin.
16+
*/
17+
String id()
18+
19+
/**
20+
* The least acceptable version of the required plugin. Optional, any version will be accepted if none is given.
21+
*/
22+
String minimumVersion() default ''
23+
}

job-dsl-core/src/main/groovy/javaposse/jobdsl/dsl/helpers/publisher/GitPublisherContext.groovy

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import javaposse.jobdsl.dsl.Context
44
import javaposse.jobdsl.dsl.ContextHelper
55
import javaposse.jobdsl.dsl.DslContext
66
import javaposse.jobdsl.dsl.JobManagement
7+
import javaposse.jobdsl.dsl.RequiresPlugin
78

89
import static com.google.common.base.Preconditions.checkArgument
910
import static com.google.common.base.Strings.isNullOrEmpty
@@ -28,8 +29,8 @@ class GitPublisherContext implements Context {
2829
this.pushMerge = pushMerge
2930
}
3031

32+
@RequiresPlugin(id = 'git', minimumVersion = '2.2.6')
3133
void forcePush(boolean forcePush = true) {
32-
jobManagement.requireMinimumPluginVersion('git', '2.2.6')
3334
this.forcePush = forcePush
3435
}
3536

job-dsl-core/src/main/groovy/javaposse/jobdsl/dsl/helpers/publisher/HtmlReportTargetContext.groovy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package javaposse.jobdsl.dsl.helpers.publisher
22

33
import javaposse.jobdsl.dsl.Context
44
import javaposse.jobdsl.dsl.JobManagement
5+
import javaposse.jobdsl.dsl.RequiresPlugin
56

67
class HtmlReportTargetContext implements Context {
78
private final JobManagement jobManagement
@@ -29,9 +30,8 @@ class HtmlReportTargetContext implements Context {
2930
this.keepAll = keepAll
3031
}
3132

33+
@RequiresPlugin(id = 'htmlpublisher', minimumVersion = '1.3')
3234
void allowMissing(boolean allowMissing = true) {
33-
jobManagement.requireMinimumPluginVersion('htmlpublisher', '1.3')
34-
3535
this.allowMissing = allowMissing
3636
}
3737
}

job-dsl-core/src/main/groovy/javaposse/jobdsl/dsl/helpers/publisher/PublisherContext.groovy

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import javaposse.jobdsl.dsl.Context
88
import javaposse.jobdsl.dsl.ContextHelper
99
import javaposse.jobdsl.dsl.DslContext
1010
import javaposse.jobdsl.dsl.JobManagement
11+
import javaposse.jobdsl.dsl.RequiresPlugin
1112
import javaposse.jobdsl.dsl.WithXmlAction
1213
import javaposse.jobdsl.dsl.helpers.common.BuildPipelineContext
1314
import javaposse.jobdsl.dsl.helpers.common.DownstreamContext
@@ -66,6 +67,7 @@ class PublisherContext implements Context {
6667
extendedEmail(recipients, subjectTemplate, null, emailClosure)
6768
}
6869

70+
@RequiresPlugin(id = 'email-ext')
6971
void extendedEmail(String recipients, String subjectTemplate, String contentTemplate,
7072
@DslContext(EmailContext) Closure emailClosure = null) {
7173
EmailContext emailContext = new EmailContext()
@@ -352,9 +354,8 @@ class PublisherContext implements Context {
352354
* </plots>
353355
* </hudson.plugins.plot.PlotPublisher>
354356
*/
357+
@RequiresPlugin(id = 'plot', minimumVersion = '1.9')
355358
void plotBuildData(@DslContext(PlotsContext) Closure plotsClosure) {
356-
jobManagement.requireMinimumPluginVersion('plot', '1.9')
357-
358359
PlotsContext plotsContext = new PlotsContext()
359360
ContextHelper.executeInContext(plotsClosure, plotsContext)
360361

@@ -1686,9 +1687,9 @@ class PublisherContext implements Context {
16861687
* }
16871688
* </pre>
16881689
*/
1690+
@RequiresPlugin(id = 'warnings', minimumVersion = '4.0')
16891691
void warnings(List consoleParsers, Map parserConfigurations = [:],
16901692
@DslContext(WarningsContext) Closure warningsClosure = null) {
1691-
jobManagement.requireMinimumPluginVersion('warnings', '4.0')
16921693
WarningsContext warningsContext = new WarningsContext()
16931694
ContextHelper.executeInContext(warningsClosure, warningsContext)
16941695

0 commit comments

Comments
 (0)