Jenkins User Conference 2012
Only by the third plugin do you get the hang of writing a plugin. I thought as a developer coming to the build side of things it'd be easy to jump in and write some plugins. I was wrong. Don't be fooled by the extremely friendly Jenkins community, writing a plugin from scratch is harder than they let on. This talk will explain the hurdles that I had to cross to make writing plugins easy.
2. Disclaimer
Jenkins has evolved
There will be zombies
[http://www.squidoo.com/zombies-hq]
[http://www.evike.com/product_info.php?products_id=27951]
3. Disclaimer
Jenkins has evolved
There will be zombies
[http://www.squidoo.com/zombies-hq]
[http://www.evike.com/product_info.php?products_id=27951]
4. Disclaimer
Jenkins has evolved
There will be zombies
[http://www.squidoo.com/zombies-hq]
[http://www.evike.com/product_info.php?products_id=27951]
5. Disclaimer
Jenkins has evolved
There will be zombies
Survival Tip:
Use your instincts
[http://www.squidoo.com/zombies-hq]
[http://www.evike.com/product_info.php?products_id=27951]
6. Why do we write plugins?
“An extendable open
source continuous
integration server”
[http://www.ufunk.net/en/photos/dead-of-the-class-lavantapres-des-zombies/]
7. Don’t write a plugin!
Boilerplate
Maintenance
Overhead
[http://www.ufunk.net/en/photos/dead-of-the-class-lavantapres-des-zombies/]
9. Groovy Plugin
Access to complete system
Sample:
Disable failing jobs
Scan for known errors
Enforce plugin defaults
[http://www.spencersonline.com/product/n-zombie-brains-poster/]
10. Groovy Plugin
def buildable = Jenkins.instance.projects
.findAll { !it.getBuilds().isEmpty() && it.buildable }
def bad = "Illegal attempt to republish existing"
buildable.each { job ->
lastBuild = job.getLastBuild()
log = lastBuild.getLog(100)
logLength = log.size()
int lineNum = 0
for (; lineNum < logLength && !errorFound; lineNum++) {
if (log[lineNum].contains(bad)) {
print "Error in ${job.name} “
println “build ${lastBuild.number} on line ${lineNum}"
errorFound = true;
}
}
}
22. Descriptor
Needed for each
Describable
Hold Global Config
Creates Describable
[http://www.sandovalnation.webs.com/]
23. Extension Points
“Hooks into system”
@ExtensionPoint indicates
implementation
Most Common:
BuildWrapper
Trigger
BuildStep
Publisher
[http://s268.photobucket.com/albums/jj23/cooljay1622/?action=view¤t=ZombieArms.png&newest=1]
24. Extension Points
“Hooks into system”
@ExtensionPoint indicates
implementation
Most Common:
BuildWrapper
Trigger
BuildStep
Publisher
25. What is a HPI?
Just a .jar
META-INF/MANIFEST.MF
WEB-INF
classes
META-INF
lib
Isolated Classloader
[http://zombieapocalypseacademy.org/zombie-wallpapers/]
26. What is a Plugin?
HPI Published to
Jenkins Maven
Documented in Jenkins
Wiki
[http://ismashphone.com/2011/08/plug-in-zone-out-die-english-tabloid-warns-of-ipod-zombies.html]
28. UI Model
Browser
Stapler
GET POST
Ajax
Jelly Describable Descriptor
29. Stapler
Technically a URL
mapping system
Responsible for
Loading UI
Form submissions
View “Structured Form
Submission” Wiki
[http://www.bitrebels.com/geek/zombie-skull-pencil-holder-for-the-horrific-office-worker/]
37. Break
What have I not talked
about?
[http://www.seattlepi.com/local/slideshow/Zombie-Walk-in-Fremont-45631.php]
38. Jelly
JSP-like
Placed in a package with
name of Describable
[http://www.crawlofthedead.com/article/the_zombie_brain_jelly_mould/]
39. Jelly
JSP-like
Placed in a package with
name of Describable
Annoying to
write in groovy
[http://www.crawlofthedead.com/article/the_zombie_brain_jelly_mould/]
40. Jelly Eclipse will not
like the
JSP-like package name
Placed in a package with
name of Describable
Annoying to
write in groovy
[http://www.crawlofthedead.com/article/the_zombie_brain_jelly_mould/]
46. Jenkins Singleton
Top-level access to
Jenkins data model
Made up of ItemGroup
filled with Item
[http://zombiecombatcommand.com/]
47. Jenkins Singleton
Top-level access to Ignore
Jenkins data model Hudson.getInstance()
Made up of ItemGroup
filled with Item
[http://zombiecombatcommand.com/]
49. Jenkins Singleton
Ignore
StaplerRequest
Top-level access to
Jenkins data model Methods
Made up of ItemGroup
filled with Item
50. Jenkins Singleton
Ignore
StaplerRequest
Top-level access to
Jenkins data model Methods
Made up of ItemGroup
filled with Item Ignore doCli/
doCreateView
51. Jenkins Singleton
Ignore
StaplerRequest
Top-level access to
Jenkins data model Beware Methods
synchronized
Made up of ItemGroup
filled with Item methods Ignore doCli/
doCreateView
52. Jenkins Singleton
Ignore
StaplerRequest
Top-level access to
Jenkins data model Beware Methods
synchronized
Made up of ItemGroup
filled with Item methods Ignore doCli/
rebuildDependencyGraph doCreateView
when changing Upstream/
Downstream
53. Jenkins Singleton
getItem is useful if Ignore
you know access to StaplerRequest
Top-level the name
Jenkins data model Beware Methods
synchronized
Made up of ItemGroup
filled with Item methods Ignore doCli/
rebuildDependencyGraph doCreateView
when changing Upstream/
Downstream
54. Jenkins Singleton
getItem is useful if Ignore
you know access to StaplerRequest
Top-level the name
Jenkins data model Beware Methods
synchronized
Made up of ItemGroup
filled with Item methods Ignore doCli/
rebuildDependencyGraph doCreateView
when changing Upstream/
Downstream
Never call
onRename/
OnDeleted
55. Jenkins Singleton
getItem is useful if Ignore
you know access to StaplerRequest
Top-level the name
Jenkins data model Beware Methods
synchronized
Made up of ItemGroup
filled with Item methods Ignore doCli/
rebuildDependencyGraph doCreateView
when changing Upstream/
Downstream
Never call
getPlugin to get onRename/
other plugins OnDeleted
57. Actions
Badly Named
Metadata tacked on
Persisted when
attached to Build
[http://tnation.t-nation.com/free_online_forum/music_movies_girls_life/zombie_apocalypse_choose_your_weapon]
58. Actions
Badly Named
Metadata tacked on
Persisted when
attached to Build
Keep Actions on Build
[http://tnation.t-nation.com/free_online_forum/music_movies_girls_life/zombie_apocalypse_choose_your_weapon]
59. FilePath
Very likely that a “file” is remote
AbstractBuild.getWorkspace()
FileCallable
[http://www.cisionwire.com/livingsocial-uk/i/the-horde,c148133]
60. FilePath
// make 'file' a fresh empty directory.
file.act(new FileCallable<Void>() {
// if 'file' is on a different node, this FileCallable will
// be transfered to that node and executed there.
public Void invoke(File f,VirtualChannel channel) {
// f and file represents the same thing
f.deleteContents();
f.mkdirs();
}
});
63. Justin Ryan
jryan@netflix.com
@quidryan
Netflix is hiring!
techblog.netflix.com
netflix.github.com
[http://calitreview.com/7933]
Notes de l'éditeur
Why are you here?\n\nEither you&#x2019;ve written a plugin and had no idea what you&#x2019;re doing. Or.... Or.... you&#x2019;ve never written a plugin.\n\nI&#x2019;m here to simplify things, make things clear which weren&#x2019;t once before. I&#x2019;m also trying to capture my experiences as a newbie, before I&#x2019;m not one anymore.\n\nI know I&#x2019;m going to have people come up to me afterwards to tell me how easy things are. Well, you all wouldn't be here if you thought the same thing. Maybe we can mob them afterwards.\n\nI&#x2019;ll be making a lot of references to websites and plugins. There&#x2019;s no need to write them all down in your notepad or go clickety-clack on your keyboard, I&#x2019;ll put the slides up later, with nice hyperlinks. Follow @quidryan to see where post them.\n\nNo easy answer here. I don't have sage advice, I have war wounds.\n
http://www.squidoo.com/zombies-hq\nhttp://www.evike.com/product_info.php?products_id=27951\n\nI believe what Jenkins has accomplished is nothing short of amazing. One particular miracle they&#x2019;ve pulled off is being backwards compatible for so many versions. This has the side effect of certain core objects showing their age. Which means as you traverse the documentation, you will have to waste precious cycles of your brain. Don&#x2019;t let Jenkins eat your brains.\n\nI should add that during the presentation, I might make disparaging remarks concerning the documentation. I fully understand that as an open source project the onus is on us to be fixing it as we see it. But I didn&#x2019;t realize my qualms until I worked through this presentation. It just proves that we have selective memories, while working my plugns I just pushed through until it worked and forgot all the bad parts.\n
http://www.squidoo.com/zombies-hq\nhttp://www.evike.com/product_info.php?products_id=27951\n\nI believe what Jenkins has accomplished is nothing short of amazing. One particular miracle they&#x2019;ve pulled off is being backwards compatible for so many versions. This has the side effect of certain core objects showing their age. Which means as you traverse the documentation, you will have to waste precious cycles of your brain. Don&#x2019;t let Jenkins eat your brains.\n\nI should add that during the presentation, I might make disparaging remarks concerning the documentation. I fully understand that as an open source project the onus is on us to be fixing it as we see it. But I didn&#x2019;t realize my qualms until I worked through this presentation. It just proves that we have selective memories, while working my plugns I just pushed through until it worked and forgot all the bad parts.\n
http://www.squidoo.com/zombies-hq\nhttp://www.evike.com/product_info.php?products_id=27951\n\nI believe what Jenkins has accomplished is nothing short of amazing. One particular miracle they&#x2019;ve pulled off is being backwards compatible for so many versions. This has the side effect of certain core objects showing their age. Which means as you traverse the documentation, you will have to waste precious cycles of your brain. Don&#x2019;t let Jenkins eat your brains.\n\nI should add that during the presentation, I might make disparaging remarks concerning the documentation. I fully understand that as an open source project the onus is on us to be fixing it as we see it. But I didn&#x2019;t realize my qualms until I worked through this presentation. It just proves that we have selective memories, while working my plugns I just pushed through until it worked and forgot all the bad parts.\n
Then one day you get one little use case you think needs some tweaking, so you decide it time to write a plugin. Right? You&#x2019;ve heard how Jenkins is made up from Plugins, and there&#x2019;s a friendly community behind them.\n\nSure, we all think if we write a Jenkins plugins we'll get famous. They&#x2019;ll put out the red carpet at Devoxx, you get interviewed on the The Ship Show Podcast, the Java Posse will give you a mention.\n
Wrong!\n\nIn your head is one or two lines which truly represent when you want to accomplish. But you&#x2019;ll realize that writing a plugin is work. There&#x2019;s boilerplate code you have to write. You have to maintain it.\n\n
One direction to go into is one of these three.\n\n
Available at the system level or in a job. Use system groovy when possible. It can go back and clean up jobs, enforce plugins. It has the advantage of being easy to code. Can be scheduled at regular intervals, when put into a Job. Plugins typically have to be enabled on a Job, which is hard when you have hundreds of jobs.\n\nExample: Disable failing jobs - People are going to be move on a project, or forget job. it&#x2019;s not worth tracking them down. So after a period of time, we just disable the jobs. &#x201C;If a builds get disabled in the Jenkins and no one notices, did really happen?&#x201D; We also limit the number of builds to 20, unless they really want more\n
I&#x2019;ll show code examples, I don&#x2019;t think it&#x2019;s the best use of time to review them line by line.\n\nIn this sample, I&#x2019;m going to each Job, aka Project, and looking at the log from the last build. \n
Displays groovy objects, useful for exploration of Jenkins data model.\n
Badges - addInfoBadge, addWarningBadge, addErrorBadge\nSummary - createSummary\nStatus - buildUnstable, buildFailure, buildSuccess\nAccess to Jenkins instance, Build and Listener\nSearching - logContains, contains to search file, getMatch, getLogMatcher\n\nCan&#x2019;t be shared though. Needs scriptler integration.\n
Examples looks for Sun priprietary APIs.\n
You&#x2019;ll have a hard time telling me this isn&#x2019;t from a plugin.\n
\n
Allows programmatic approach to creating jobs\nCan come from source control. Can be accompanied with a job.\nWhich also gives you a lot of power to share common functionality.\nScales real nicely to build a few similar projects, instead of being tempted to parameterize them\n\n
Sample loops through all the branches, creating a job for each one.\n
OK, Fine, you want to write a plugin\n\nYou start with https://wiki.jenkins-ci.org/display/JENKINS/Plugin+tutorial but you&#x2019;ll find nothing for how to code for it. It&#x2019;ll talk just about setting up your environment\nIt&#x2019;ll forward you to Stephen Connolly's 7 part tutorial, while thorough, it is bogged down maven mentuia and 5 years old\nMaybe you look at the hello-world plugin. But you still don&#x2019;t know what is boilerplate and what should be changed.\nExisting tutorials show how to create a simple plugin. I&#x2019;m taking a orthogonal approach today.\n\n
There&#x2019;s a lot of independent projects being used here, e.g. Java, Stapler, Jelly, HTML\n\nThe most important thing for me to build a mental model.\n\nAlong the lines of learning Hibernate and not learning how Sessions work. You&#x2019;ll be left dazed and confused.\n\nIn our field, you need to ask questions, but sometimes you&#x2019;re so far removed from the problem, that you don&#x2019;t even know what questions to ask.\nThe rest of the talk is about diving a little deeper into each of these technologies so that you know what do look into when you come across them. \n\nThere&#x2019;s a few way to slice and dice the models.\n\n
Let&#x2019;s start with what you know. Well, you don&#x2019;t know that you know it.\n\nMaking a Describable allows you to define help files, form validation check methods, and so on correctly. \n\n https://github.com/jenkinsci/git-plugin/blob/master/src/main/java/hudson/plugins/git/GitSCM.java\n
\n
Think of them attached as your implementations of your plugin. We&#x2019;ll come back to how it&#x2019;s viewed later, but here are some examples.\n\nCan always look it up:\n DescriptorImpl descriptor = Jenkins.getInstance().getDescriptorByType(DescriptorImpl.class);\nDescriptors are funny. Descriptor.load() in constructor, you&#x2019;re responsibility to save(). \nnewInstance used to constantly create per-Project instances. Interestingly this calls stapler to do DataBoundConstructor for you, which is why you can ignore it, until you can&#x2019;t.\nNothing stopping you from having multiple Descriptors in your plugin, where each \nBest done with a @Extension static class, will get created by Jenkins\nData can live on descriptor, because that&#x2019;s global \nForced to use configure to get values, everyone should read:\n https://wiki.jenkins-ci.org/display/JENKINS/Structured+Form+Submission\n Most basic level: useFrench = json.getBoolean("useFrench");\n More complex: req.bindJSONToList(AnsiColorMap.class, req.getSubmittedForm().get("colorMap")\n Then always call save()\n\n
Extension points provides answers to the question, &#x201C;are there any implementations for this interface?&#x201D; It then requires a consumer to call Jenkins.getInstance().getExtensionList(Class<T>);\n
Up to consumer how it&#x2019;ll collect the extension points. Each one has it&#x2019;s own rules for how it&#x2019;ll be used.\nStick with basics:\n BuildWrapper\n preCheckout\n setup, after checkout\n BuildStep\n Publisher\n&#x201C;The save operation happens without any notice, and the restore operation happens without calling the constructor, just like Java serialization.&#x201D; \n
http://zombieapocalypseacademy.org/zombie-wallpapers/\n\nFirst, let&#x2019;s talk about what is a HPI\nIt&#x2019;ll build to real binaries, one jar and one hpi.\nJust unzip look around\nMETA-INF/MANIFEST.MF - Manifest, tells you about version numbers\nWEB-INF/classes - .class files and jelly files\nWEB-INF/classes/META-INF - Output from annotation scanning\nWEB-INF/lib just has the jars\nBinary. Wrapped with all dependencies, relying on Classloaders to isolate, like OSGI\n\n\nMANIFEST.MF\nManifest-Version: 1.0\nv: 1.0-beta\nUrl: https://wiki.jenkins-ci.org/display/JENKINS/Job+DSL+Plugin\nGroup-Id: org.jenkinsci.plugins\nJenkins-Version: 1.456\nPlugin-Version: 1.0-beta\nLong-Name: Job DSL\nShort-Name: job-dsl\n
It&#x2019;s an HPI. Really, it has no other meaning, there is no Plugin class that gets implemented.\n\n
\n
\n
It is not a modern web framework. It is what it is.\nMost commonly needed to map HTML forms and bind the request to objects.\nRequired for Descriptor.configure.\nBest possible source is Structured Form Submission (https://wiki.jenkins-ci.org/display/JENKINS/Structured+Form+Submission) and http://stapler.kohsuke.org/\nThe lesson is to use good consistent names and everything will be easy.\n\n
Let&#x2019;s start talking about how they&#x2019;re loaded, I&#x2019;m going to skim over UI pieces\n\n* Startup - Loads HPI, Finds Extensions, Instantiates Descriptions\n* Global Config\nLoads &#x201C;src/main/resources/${plugin-package}/${plugin-class-name}/global.jelly&#x201D;\nsave calls configure(StaplerRequest req, JSONObject json)\n* Job Config\nLoads &#x201C;src/main/resources/${plugin-package}/${plugin-class-name}/config.jelly&#x201D;\n(Loading values via XSTREAM, then applying via Jelly)\n
Let&#x2019;s start talking about how they&#x2019;re loaded, I&#x2019;m going to skim over UI pieces\n\n* Startup - Loads HPI, Finds Extensions, Instantiates Descriptions\n* Global Config\nLoads &#x201C;src/main/resources/${plugin-package}/${plugin-class-name}/global.jelly&#x201D;\nsave calls configure(StaplerRequest req, JSONObject json)\n* Job Config\nLoads &#x201C;src/main/resources/${plugin-package}/${plugin-class-name}/config.jelly&#x201D;\n(Loading values via XSTREAM, then applying via Jelly)\n
Let&#x2019;s start talking about how they&#x2019;re loaded, I&#x2019;m going to skim over UI pieces\n\n* Startup - Loads HPI, Finds Extensions, Instantiates Descriptions\n* Global Config\nLoads &#x201C;src/main/resources/${plugin-package}/${plugin-class-name}/global.jelly&#x201D;\nsave calls configure(StaplerRequest req, JSONObject json)\n* Job Config\nLoads &#x201C;src/main/resources/${plugin-package}/${plugin-class-name}/config.jelly&#x201D;\n(Loading values via XSTREAM, then applying via Jelly)\n
Let&#x2019;s start talking about how they&#x2019;re loaded, I&#x2019;m going to skim over UI pieces\n\n* Startup - Loads HPI, Finds Extensions, Instantiates Descriptions\n* Global Config\nLoads &#x201C;src/main/resources/${plugin-package}/${plugin-class-name}/global.jelly&#x201D;\nsave calls configure(StaplerRequest req, JSONObject json)\n* Job Config\nLoads &#x201C;src/main/resources/${plugin-package}/${plugin-class-name}/config.jelly&#x201D;\n(Loading values via XSTREAM, then applying via Jelly)\n
I don&#x2019;t think it&#x2019;s relevant, but it creates URL for your classes, which have backend values for ajax calls and pages for your plugins.\ndoCheckFoo - Validation\ndoFillFooItems - Auto-populate field, used by jclouds plugin to fill in hardware types\ndoAutocompleteFoo - Contributes to auto complete values\n\n
From hudson.scm.SubversionSCM\n\nCame from this:\n<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">\n <f:section title="${%Subversion}">\n <f:entry title="${%Subversion Workspace Version}" help="/descriptor/hudson.scm.SubversionSCM/help/workspaceFormat">\n <select name="svn.workspaceFormat">\n <f:option value="8" selected="${descriptor.workspaceFormat==8}" >1.4</f:option>\n <f:option value="9" selected="${descriptor.workspaceFormat==9}" >1.5</f:option>\n <f:option value="10" selected="${descriptor.workspaceFormat==10}">1.6 (svn:externals to file)</f:option>\n </select>\n </f:entry>\n <f:entry title="${%Exclusion revprop name}" help="/descriptor/hudson.scm.SubversionSCM/help/excludedRevprop">\n <f:textbox name="svn.global_excluded_revprop" value="${descriptor.globalExcludedRevprop}"/>\n </f:entry>\n <f:optionalBlock\n name="svn.validateRemoteUpToVar"\n checked="${descriptor.validateRemoteUpToVar}"\n title="${%Validate repository URLs up to the first variable name}"\n help="/descriptor/hudson.scm.SubversionSCM/help/validateRemoteUpToVar"/>\n <f:optionalBlock\n name="svn.storeAuthToDisk"\n checked="${descriptor.storeAuthToDisk}"\n title="${%Update default Subversion credentials cache after successful authentication}"/>\n </f:section>\n</j:jelly>\n
\n
\nCan write in Groovy, which is a facade over jelly. But it's even worse documented\nui-samples plugin is the best reference for groovy examples. Though I was tripped up by s:sample notation.\nStapler identifies appropriate file to render\nTags are run. Everything is executed on the server. Though some will output HTML that could come with javascript to run.\nPage renders\nCallbacks to fillFooItems, prototype.js behind the scenes.\n\n
\nCan write in Groovy, which is a facade over jelly. But it's even worse documented\nui-samples plugin is the best reference for groovy examples. Though I was tripped up by s:sample notation.\nStapler identifies appropriate file to render\nTags are run. Everything is executed on the server. Though some will output HTML that could come with javascript to run.\nPage renders\nCallbacks to fillFooItems, prototype.js behind the scenes.\n\n
\n/jenkins/tree/master/core/src/main/resources/lib/[hudson,layout,form]\njelly:core\nwhile, choose/when/otherwise, \nPointless: core:scope, core:thread, core:parse, core:mute\n/lib/hudson\n/lib/form\nSection, almost necessary div identifying your plugin verses others\n<f:section title="Environment Selector Set-up">\nEntry\nDoes magic with doCheck, and binding help-*.html foo, auto population, \nExcept for boolean/checkboxes\n<f:entry title="${%Environment}">\nTextbox\nShouldn&#x2019;t need name and value, if wrapped in entry\n<f:textbox name="env.envName" value="${env.envName}"/>\nRuby doesn&#x2019;t have it much better:\nhttps://github.com/rtyler/vagrant-plugin/blob/master/views/vagrant/sudo_builder/config.erb\n\n
Simplified because anything Describable can have a view\n
\nDocumentation is typically poor, so just install them. It's so crazy easy to run Jenkins locally.\nThere will be no end to people telling you to look at existing plugins.\nWork with modern plugins, with @Extensions, like\n Associated File Plugin - For Gradle Example\n https://github.com/jenkinsci/hello-world-plugin\n https://wiki.jenkins-ci.org/display/JENKINS/TEPCO+Plugin For Widget, Recurring work\n Don&#x2019;t use static analysis plugins and Job Property Plugin\n\n
Number of installations per plugin follows a steep hockey curve. If you ignore the core, all plugins have a real limited install base. There is a daunting number of plugins out there. It&#x2019;s hard to know what is new/old/modern.\n
This graph represents just the middle. The bar isn&#x2019;t that high though. SBT is a major build tool, and it&#x2019;s at 310. Then again, there&#x2019;s a emma plugin which mis-spells column and a plugin that just shows Bruce Scheneier is here. But at the same time there&#x2019;s plugins which serve a small community, that doesn&#x2019;t make it old or worthless.\n
http://javadoc.jenkins-ci.org/jenkins/model/Jenkins.html\n\nSpeaking of Jenkins the class, this is a place to start exploring. Starts with ItemGroup, containing a specific type of Item. "Full Name" is the join of '/'.\nKeep in mind that properties of Item as you work with objects, because it won&#x2019;t be obvious. E.g. ItemListener for things like onLoad()\nSpecial type of item: TopLevelItem\n\n
\n
\n
\n
\n
\n
\n
\n
\n
Pretty much any meta-data added to an object\nThe only real way to contribute actions to a Project is getProjectActions()\nI tried to save post-build data directly onto an project action, but it kept getting creamed. Best solution was to do with most other plugins do, which is store action on the build, then at the project level aggregate them.\n\n\n\n
You can no longer assume files are local. Welcome to the world of distributed computing.\nJenkins will let you fake a synchronous call, via FileCallable. Let&#x2019;s you ship code to slave to run.\n&#x201C;On a slave, usually only a part of the Jenkins object graph is available. This means that, for instance, Hudson.getInstance() can return null. To work around this, grab all the information you need on the master side, assign them to final variables, then access them from the closure so that only those bits get sent to the slave.&#x201D;\nYou get one from something like boolean perform(AbstractBuild<?,?> build, Launcher launcher, BuildListener listener)\n\n
\n
There's a long history of tight maven integration. Maven is not as bad as long as you have someone else writing the plugins, luckily that&#x2019;s already done for Jenkins. Gradle is new enough that could cause confusion for new developers, especially without a Java background.\nThe only reason I used it was that I had Hans Dockter and Andrew Bayer around.\nI ran into issues around requiring maven plugin support, testCompile needing exclusions.\n\n
I&#x2019;d like to thank our sponsors. Having a conference like this is really important to the community.\n