Skip to content

Commit

Permalink
Introduce JenkinsServerTask
Browse files Browse the repository at this point in the history
- Adds jenkinsServerRuntimeOnly configuration
- Launches server as JavaExec task
- Allow JavaExecSpec to override server defaults
- Docs for server behavior and customization
- Backwards compatibility for debug properties passed through Gradle

Fixes #151
  • Loading branch information
sghill committed May 17, 2020
1 parent b4d5041 commit 8db6dfd
Show file tree
Hide file tree
Showing 6 changed files with 229 additions and 34 deletions.
121 changes: 99 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,104 @@ Examples:
local Maven repository.
* `gradle publish` - Deploy your plugin to
the Jenkins Maven repository to be included in the Update Center.
* `gradle server` - Start a local instance of Jenkins (http://localhost:8080) with the plugin pre-installed for testing
and debugging. The HTTP port can be changed with the `jenkins.httpPort` project or system property, e.g.
`gradle server -Djenkins.httpPort=8082`. Since the server runs in the Gradle process, [increasing the memory available
to Gradle][configure-jvm] also increases the memory available to Jenkins.
* `gradle server` - Start a local instance of Jenkins with the plugin pre-installed for testing
and debugging.

[configure-jvm]: https://docs.gradle.org/current/userguide/build_environment.html#sec:configuring_jvm_memory
### Running Jenkins Locally

The `server` task creates a [hpl][hpl], installs plugins, and starts up Jenkins on port 8080. The server runs
in the foreground.

Plugins added to any of these configurations will be installed to `${jenkinsPlugin.workDir}/plugins`:
- `api`
- `implementation`
- `runtimeOnly`
- `jenkinsServer`

#### Default System Properties

Jenkins starts up with these system properties set to `true`:
- `stapler.trace`
- `stapler.jelly.noCache`
- `debug.YUI`
- `hudson.Main.development`

Each can be overridden as described in the _Customizing Further_ section below.

[hpl]: https://wiki.jenkins.io/display/JENKINS/Plugin+Structure#PluginStructure-DebugPluginLayout:.hpl


#### Customizing Port

Jenkins starts by default on port 8080. This can be changed with the `--port` flag or `port` property.

For example to run on port 7000:

```
$ ./gradlew server --port=7000
```

or in build.gradle:

```groovy
tasks.named('server').configure {
it.port.set(7000)
}
```

#### Customizing Further

The `server` task accepts a [`JavaExecSpec`][javaexecspec] that allows extensive customization.

Here's an example with common options:

```groovy
tasks.named('server').configure {
execSpec {
systemProperty 'some.property', 'true'
environment 'SOME_ENV_VAR', 'HelloWorld'
maxHeapSize = '2g'
}
}
```


#### Debugging

To start Jenkins in a suspended state with a debug port of 5005, add the `--debug-jvm` flag:

```
$ ./gradlew server --debug-jvm
> Task :server
Listening for transport dt_socket at address: 5005
```

Debug options can be customized by the `server` task's `execSpec` action:

```groovy
tasks.named('server').configure {
execSpec {
debugOptions {
port.set(6000)
suspend.set(false)
}
}
}
```
```
$ ./gradlew server --debug-jvm
> Task :server
Listening for transport dt_socket at address: 6000
```



#### Additional Server Dependencies

Any additional dependencies for the `server` task's classpath can be added to the `jenkinsServerRuntimeOnly`
configuration. This can be useful for alternative logging implementations.

## Disabling SHA256 and SHA512 checksums when releasing a plugin

Expand All @@ -174,27 +266,12 @@ If you combine java and groovy code and both provide extensions you need to eith
- Use joint compilation, i.e. put your java source files into the groovy source path (src/main/groovy)
- or force Gradle to use the old layout by including something like `sourceSets.main.output.classesDir = new File(buildDir, "classes/main")` in your build.gradle as a workaround.

## Debugging

It is possible to attach a remote debugger to the Jenkins instance started by `gradle server`.

$ ./gradlew server -Dorg.gradle.debug=true

This command will run the gradle JVM with the appropriate options on the default debug port.
If more control is required, the JVM options can be set in more detail:

$ ./gradlew server -Dorg.gradle.jvmargs=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005

The `server` task enables several debug options: `stapler.trace`, `stapler.jelly.noCache` and `debug.YUI`. This
increases the page load time. All option can be changed and new options can be added by passing them as system
properties to the Gradle command line.

$ ./gradlew -Dstapler.jelly.noCache=false server

## Examples

Here are some real world examples of Jenkins plugins using the Gradle JPI plugin:

* [Job DSL Plugin](https://github.com/jenkinsci/job-dsl-plugin)
* [Selenium Axis Plugin](https://github.com/jenkinsci/selenium-axis-plugin)
* [Doktor Plugin](https://github.com/jenkinsci/doktor-plugin)

[javaexecspec]: https://docs.gradle.org/current/javadoc/org/gradle/process/JavaExecSpec.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import org.gradle.util.ConfigureUtil
*/
class JpiExtension {
final Project project
@Deprecated
Map<String, String> jenkinsWarCoordinates

JpiExtension(Project project) {
Expand Down
26 changes: 22 additions & 4 deletions src/main/groovy/org/jenkinsci/gradle/plugins/jpi/JpiPlugin.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.DependencySet
import org.gradle.api.attributes.AttributeCompatibilityRule
import org.gradle.api.attributes.AttributeDisambiguationRule
import org.gradle.api.attributes.Bundling
Expand Down Expand Up @@ -51,6 +52,7 @@ import org.gradle.util.GradleVersion
import org.jenkinsci.gradle.plugins.jpi.server.GenerateJenkinsServerHplTask

import org.jenkinsci.gradle.plugins.jpi.server.InstallJenkinsServerPluginsTask
import org.jenkinsci.gradle.plugins.jpi.server.JenkinsServerTask

import static org.gradle.api.logging.LogLevel.INFO
import static org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME
Expand Down Expand Up @@ -115,10 +117,26 @@ class JpiPlugin implements Plugin<Project> {
it.dependsOn(generateHpl)
}

gradleProject.tasks.register(ServerTask.TASK_NAME, ServerTask) {
it.description = 'Run Jenkins in place with the plugin being developed'
it.group = BasePlugin.BUILD_GROUP // TODO
it.dependsOn(ext.mainSourceTree().runtimeClasspath, generateHpl, installPlugins)
def serverRuntime = gradleProject.configurations.create('jenkinsServerRuntimeOnly') { Configuration c ->
c.withDependencies { DependencySet deps ->
deps.add(gradleProject.dependencies.create("org.jenkins-ci.main:jenkins-war:${ext.coreVersion}@war"))
}
}

gradleProject.tasks.register(JenkinsServerTask.TASK_NAME, JenkinsServerTask) {
it.description = 'Run Jenkins server locally with the plugin being developed'
it.group = 'Jenkins Server'
it.dependsOn(installPlugins)
it.jenkinsServerRuntime.set(serverRuntime)
it.jenkinsHome.set(ext.workDir)
def sysPropPort = System.getProperty('jenkins.httpPort')
if (sysPropPort) {
it.port.convention(sysPropPort)
}
def propPort = project.findProperty('jenkins.httpPort') as String
if (propPort) {
it.port.convention(propPort)
}
}

// set build directory for Jenkins test harness, JENKINS-26331
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import java.util.jar.JarFile
*
* @author Kohsuke Kawaguchi
*/
@Deprecated
class ServerTask extends DefaultTask {
public static final String TASK_NAME = 'server'

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package org.jenkinsci.gradle.plugins.jpi.server

import groovy.transform.CompileStatic
import org.gradle.api.Action
import org.gradle.api.DefaultTask
import org.gradle.api.artifacts.Configuration
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.options.Option
import org.gradle.process.JavaExecSpec

import java.util.jar.JarFile

@CompileStatic
class JenkinsServerTask extends DefaultTask {
public static final String TASK_NAME = 'server'
private static final Set<String> DEFAULTED_PROPERTIES = [
'stapler.trace',
'stapler.jelly.noCache',
'debug.YUI',
'hudson.Main.development',
] as Set
private final List<Action<JavaExecSpec>> execSpecActions = []

@Classpath
final Property<Configuration> jenkinsServerRuntime = project.objects.property(Configuration)

@Input
final Provider<File> jenkinsHome = project.objects.property(File)

@Input
@Option(option = 'port', description = 'Port to start Jenkins on (default: 8080)')
final Property<String> port = project.objects.property(String)
.convention('8080')

@Input
@Option(option = 'debug-jvm', description = 'Start Jenkins suspended and listening on debug port (default: 5005)')
final Property<Boolean> debug = project.objects.property(Boolean)
.convention(false)

@Internal
final Provider<String> extractedMainClass = jenkinsServerRuntime.map {
def war = it.resolvedConfiguration
.firstLevelModuleDependencies
.find { it.moduleGroup == 'org.jenkins-ci.main' && it.moduleName == 'jenkins-war' }
.moduleArtifacts
.find { it.extension == 'war' }
.file
new JarFile(war).manifest.mainAttributes.getValue('Main-Class')
}

JenkinsServerTask() {
doLast {
project.javaexec { JavaExecSpec s ->
s.classpath(jenkinsServerRuntime.get())
s.mainClass.set(extractedMainClass)
s.args("--httpPort=${port.get()}")
s.systemProperty('JENKINS_HOME', jenkinsHome.get())
for (String prop : DEFAULTED_PROPERTIES) {
s.systemProperty(prop, 'true')
}
passThroughForBackwardsCompatibility(s)
s.debug = debug.get()
execSpecActions.each { a -> a.execute(s) }
}
}
}

void execSpec(Action<JavaExecSpec> action) {
execSpecActions.add(action)
}

/**
* Discovers system properties set in gradle and passes them through to the task.
*
* Implemented for backwards-compatibility. Will be removed in 1.0.0.
*
* @param spec - to be mutated
*/
void passThroughForBackwardsCompatibility(JavaExecSpec spec) {
boolean anyDefined = false
for (String prop : DEFAULTED_PROPERTIES) {
def defined = System.getProperty(prop)
if (defined) {
anyDefined = true
logger.warn('Passing through system property {} to server is deprecated', prop)
spec.systemProperty(prop, defined)
}
}
if (anyDefined) {
logger.warn('Please configure server task with system properties directly')
logger.warn('Passing through will be removed in 1.0.0')
}
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
package org.jenkinsci.gradle.plugins.jpi
package org.jenkinsci.gradle.plugins.jpi.server

import org.jenkinsci.gradle.plugins.jpi.IntegrationSpec
import spock.lang.IgnoreIf
import spock.lang.Unroll

@IgnoreIf({ System.getProperty('os.name').toLowerCase().contains('windows') })
class ServerTaskSpec extends IntegrationSpec {
class JenkinsServerTaskSpec extends IntegrationSpec {

@Unroll
def 'server task is working - Jenkins #jenkinsVersion'() {
given:
projectDir.newFile('settings.gradle') << """\
projectDir.newFile('settings.gradle') << """\
rootProject.name = "test-project"
includeBuild('${path(new File(''))}')
"""
Expand Down Expand Up @@ -44,15 +45,14 @@ class ServerTaskSpec extends IntegrationSpec {
}
}

// run a separate process because the Jenkins shutdown kills the daemon
def gradleProcess = "${path(new File('gradlew'))} server -Djenkins.httpPort=8456 --no-daemon".
execute(null, projectDir.root)
def output = gradleProcess.text
def result = gradleRunner()
.withArguments('server', '--port=8456')
.build()
def output = result.output

then:
output.contains("/jenkins-war-${jenkinsVersion}.war")
output.contains('webroot: System.getProperty("JENKINS_HOME")')
new File(projectDir.root, 'work/plugins/git.hpi').exists()

where:
jenkinsVersion | additionalPlugin
Expand Down

0 comments on commit 8db6dfd

Please sign in to comment.