JavaOne Conference 2016, San Francisco: Talk by Mario-Leander Reimer (@LeanderReimer, Principal Software Architect at QAware).
Abstract: Every software project starts with the setup of a local development environment: a JDK, a preferred IDE and build tool, a local database and application server and so forth. Everything you and your team needs to be productive from day one. Time is valuable, so you take the quick route and reuse a development environment from a previous project. Broken windows from day one! With the first required changes things usually start to go wrong, the individual environments start to diverge and problems during the build or local execution of your software are inevitable. So how can you do better? The short answer is: with SEU-as-code, a lightweight approach and tool based on Gradle that helps to alleviate and automate the provisioning of developers.
2. About me
Mario-Leander Reimer
Chief Technologist, QAware GmbH
mario-leander.reimer@qaware.de
twitter://@LeanderReimer
http://speakerdeck.com/lreimer
http://github.com/lreimer
http://github.com/seu-as-code
http://seu-as-code.io
http://www.qaware.de
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 2
3. | JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 3
4. SEU: a German acronym;
software development environment
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 4
5. Definition of Software Industrialization
• This has nothing to do with cheap labor!
• Automation of repetitive and laborious tasks
• Better software quality through standardized,
streamlined tooling
• Well integrated tool chain leads to a higher
productivity and happiness of your team
• Better cost efficiency and competitiveness
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 5
6. | JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 6
7. The ideal.
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 7
8. | JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 8
9. The reality?
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 9
10. What are the problems?
• Manual and error prone creation and update
procedure of individual SEUs
• Individual SEUs produce different results and
strange bugs
• Old versions of a SEU can't easily be restored
• The SEU for a new project is often a copy of a
previous project. ! Broken windows! !
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 10
11. The solution ...
• Use a build tool for the automated creation and
update of a SEU
• Software packages are expressed as dependencies
• Gradle tasks and Groovy are used instead of
shell scripting
• Everything is version controlled just like
ordinary source code
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 11
12. Demo.
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 12
13. The most minimalistic SEU
plugins {
id 'de.qaware.seu.as.code.base' version '2.4.0'
}
seuAsCode {
seuHome = 'J:'
projectName = 'JavaOne 2016'
}
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 13
14. Multi platform SEUs for mixed teams
plugins {
id 'de.qaware.seu.as.code.base' version '2.4.0'
}
import static de.qaware.seu.as.code.plugins.base.Platform.isMac
seuAsCode {
// use closure or Elvis operator (no syntax highlighting :(
seuHome = { if (isMac()) '/Volumes/JavaOne-2016' else 'J:' }
projectName = 'JavaOne 2016'
}
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 14
15. Software packages are dependencies
• Gradle has built-in support for dependencies
• Gradle supports different artifact repositories
(Maven, Ivy, Directory)
• Transitive dependencies between software
packages are supported
• Use dependency configurations to determine
installation directory
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 15
16. Software dependencies and configurations
repositories {
maven { url 'https://dl.bintray.com/seu-as-code/maven' }
jcenter()
}
dependencies {
seuac 'org.codehaus.groovy.modules.scriptom:scriptom:1.6.0'
seuac 'com.h2database:h2:1.4.188'
home 'de.qaware.seu.as.code:seuac-home:2.4.0'
software 'de.qaware.seu.as.code:seuac-environment:2.4.0:jdk8'
software 'org.gradle:gradle:2.14.1'
software "net.java:openjdk8:8u40:$osClassifier"
}
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 16
17. Secure passwords with Credentials plugin
plugins { id 'de.qaware.seu.as.code.credentials' version '2.4.0' }
repositories {
maven {
url 'https://your.company.com/nexus/repo'
credentials {
// access stored credentials via extra property
username project.credentials['Nexus'].username
password project.credentials['Nexus'].password
}
}
}
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 17
18. Platform plugin for OS specific builds
plugins { id 'de.qaware.seu.as.code.platform' version '1.0.0' }
platform {
win {
task helloSeuAsCode(group: 'Example') << {
println 'Hello SEU-as-code on Windows.'
}
}
mac {
task helloSeuAsCode(group: 'Example') << {
println 'Hello SEU-as-code on MacOS.'
}
}
}
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 18
19. Manage sources with Git or SVN plugin
plugins {
id 'de.qaware.seu.as.code.credentials' version '2.4.0'
id 'de.qaware.seu.as.code.git' version '2.3.0'
}
git {
plugins {
url 'https://github.com/seu-as-code/seu-as-code.plugins.git'
directory file("$seuHome/codebase/plugins/")
username project.credentials['Github'].username
password project.credentials['Github'].password
}
}
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 19
20. Manage sources with Git or SVN plugin
plugins {
id 'de.qaware.seu.as.code.credentials' version '2.4.0'
id 'de.qaware.seu.as.code.svn' version '2.1.1'
}
subversion {
usersGuide {
url 'https://github.com/seu-as-code/seu-as-code.users-guide.git'
directory file("$seuHome/docbase/users-guide/")
username project.credentials['Subversion'].username
password project.credentials['Subversion'].password
}
}
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 20
21. Implement scripts using plain Gradle
• Gradle build scripts are code! Be creative.
• Gradle provides useful predefined tasks: Copy,
Exec, Sync, Delete, ...
• Gradle tasks can easily reuse Ant tasks.
• Gradle tasks can be implemented in Groovy code.
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 21
22. Example 1: Init and delete Derby DB
ext.derbyHome = System.env['DERBY_HOME']
// import SQL file via the provided Derby command line tool
task initDemoDb(type: Exec, group: 'Database') {
workingDir "$derbyHome/bin"
commandLine 'cmd', '/B', '/C', 'ij.bat', "$rootDir/scripts/init.sql"
}
task deleteDemoDb(type: Delete, group: 'Database') {
delete "$derbyHome/demo"
}
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 22
23. Example 1: The Groovy SQL way
import groovy.sql.*
task initDemoDb(group: 'Database') << {
def derby = [
url: 'jdbc:derby://localhost:1527/demo;create=true',
driver: 'org.apache.derby.jdbc.ClientDriver',
user: 'admin',
password: 'secret'
]
Sql.withInstance(derby) {
execute file("$rootDir/scripts/database/init.sql").text
}
}
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 23
24. Example 2: Restore Solr index data
ext.cores = ['de_DE', 'en_GB', 'zh_CN']
task solrRestoreAll(group: 'Solr') { }
cores.each { coreName ->
def name = coreName.replaceAll("_", "").capitalize()
task "solrRestore${name}"(type: Sync, group: 'Solr') {
from zipTree("$rootDir/scripts/solr/${coreName}.zip")
into "$seuHome/software/solr-4.7.2/example/solr/${coreName}"
}
solrRestoreAll.dependsOn "solrRestore${name}"
}
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 24
25. Don't use shell
scripts. Use
Gradle instead!
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 25
26. Building software packages is easy
• Software packages are essentially plain JAR
files (software + customizations)
• 26 packages available via a Bintray repository
https://bintray.com/seu-as-code/maven
• 44 package build blue-prints are available at
Github, continuously growing.
• Software packages can be build with Gradle!
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 26
27. Let's build our
first package
using Gradle!
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 27
28. | JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 28
29. Download the original binary distribution
plugins {
id 'de.undercouch.download' version '1.2'
}
import de.undercouch.gradle.tasks.download.Download
task downloadArchive(type: Download) {
src 'https://services.gradle.org/distributions/gradle-3.0-all.zip'
dest "$buildDir"
}
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 29
30. Unpack archive using simple Gradle task
task extractArchive(type: Copy, dependsOn: downloadArchive) {
from {
zipTree("$buildDir/${project.name}-${version}-all.zip")
}
into "$buildDir/files"
}
// Best practice: directory name matches the package name
// optionally rename the extracted directory if required
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 30
31. Customize with additional files and hooks
// plain Groovy, place as a file under META-INF/hooks/
import org.codehaus.groovy.scriptom.*
Scriptom.inApartment
{
def wshShell = new ActiveXObject("WScript.Shell")
def shortcut = wshShell.CreateShortcut("$seuHomegradle.lnk")
shortcut.TargetPath = "${seuLayout.software}go-gradle.bat"
shortcut.WorkingDirectory = "${seuLayout.codebase}"
shortcut.Save()
}
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 31
32. Repackage all files as a normal JAR file
task buildPackage(type: Jar, dependsOn: extractArchive) {
baseName project.name
version version
extension 'jar'
// classifier 'x86'
destinationDir buildDir
from "$buildDir/files" // the extracted files
from "files" // some extra files
}
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 32
33. Publish JAR artifact to a repository server
publishing {
publications {
gradle(MavenPublication) {
artifact "${buildDir}/${project.name}-${version}.jar"
}
}
repositories {
maven {
// alternatively, use your private company repository server
url 'https://bintray.com/seu-as-code/maven'
credentials { ... }
}
}
}
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 33
34. What's next?
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 34
35. More features to come ...
• Continuously add more software packages
• Create plugin to build software packages
• Support for self-downloading packages
• Add support for other package managers
• Add Vault support for Credentials plugin
• Improve documentation and user’s guide
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 35
36. Star SEU-as-code on Github!
$ git clone https://github.com/seu-as-code/seu-as-code.archetype.git
$ git clone https://github.com/seu-as-code/seu-as-code.plugins.git
$ git clone https://github.com/seu-as-code/seu-as-code.packages.git
$ git clone https://github.com/seu-as-code/seu-as-code.examples.git
$ git clone https://github.com/seu-as-code/seu-as-code.documentation.git
$ git clone https://github.com/seu-as-code/seu-as-code.users-guide.git
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 36
37. Q & A
| JavaOne 2016 | task seuAsCode() << { println "created with ❤ and ☕ by @LeanderReimer" } 37