why your build matters

52
Why your build matters Peter Ledbrook e: [email protected] w: http://www.cacoethes.co.uk t: @pledbrook

Upload: peter-ledbrook

Post on 10-May-2015

444 views

Category:

Technology


0 download

DESCRIPTION

Explains why software builds are important and what build tools should offer to users in terms of features. It also looks at how Gradle satisfies these requirements.

TRANSCRIPT

Page 1: Why your build matters

Why your build mattersPeter Ledbrook

e: [email protected] w: http://www.cacoethes.co.uk t: @pledbrook

Page 2: Why your build matters

When did it all begin?

• Programmable machines have a long history

• Mechanised instruments

• Looms

Page 3: Why your build matters

Punched cards

First used for looms in 18th Century France - Jacquard Loom 1801 IBM introduced punched card format 1928 - 80 columns! Text mode column width for DOS, Unix etc. Imagine creating by hand Keypunch machines (a little like typewriters) for creating them Errors mean throwing away the card and doing it again Fortran and Cobol started with punched cards

Page 4: Why your build matters

Fixing card decks

What if your cards are dropped on the floor? This is an IBM 082 Sorter Automation of a very error-prone manual process

Page 5: Why your build matters

Magnetism saves the day!

Finally, R/W data and code. The dawn of text editors.

Page 6: Why your build matters

C/C++

Multi-platform builds!

Compiler

Linker

Source

.so/.dll & .exe

Page 7: Why your build matters

Make

*.c: *.o cc …!

myapp: app.o mylib.o … ld …

+ manual custom dependencies

Focus mainly on compilation and linking Make has powerful model based on file dependencies Maintenance was a lot of work

Page 8: Why your build matters

Make

*.c: *.o cc …!

myapp: app.o mylib.o … ld …

What’s this?

+ manual custom dependencies

Tabs were the bane of Make file writers

Page 9: Why your build matters

Java

• Compiler handles .java to .class dependencies

• JAR as ‘module’ unit

• Several standard deployment types

- Applet

- WAR

- Standalone app

Page 10: Why your build matters

A standard build

Compile Test Package Publish?

A simplification from the days of C

Page 11: Why your build matters

Java build evolved

Compile Test Package Publish?

Dev setup Run

QA

SCM Tags

Deployment

DocsGenerators

JS + CSS

Builds are incorporating more and more steps

Page 12: Why your build matters

What will builds look like in 5 years time?

Page 13: Why your build matters

What is common across all these systems? To understand that, let’s look at a non-software process

Page 14: Why your build matters

Making tea

Boil kettle

Pour water into pot

Empty pot

Put tea in pot

Brew for n seconds

Fill kettle

Clear that it’s a series of steps (or tasks) Some tasks require others to complete first Others can run in parallel It’s an acyclic graph of tasks!

Page 15: Why your build matters

Making tea (my way)

Boil water on hob

Add tea to pan

Brew for n seconds

Measure water into pan

A different way to make tea Less standard, more appropriate for me

Page 16: Why your build matters

A standard process does not mean an exclusive one

Page 17: Why your build matters

Many things can go wrong

Boil kettle

Pour water into pot

Empty pot

Put tea in pot

Brew for n seconds

Fill kettle

Forget to empty pot

Brew for too long

Put too little or too much water in

Page 18: Why your build matters

Automate to eliminate human error

People are fallible Muscle memory can help e.g. tying shoe laces Not usually applicable in software development

Page 19: Why your build matters

What if your job depends on perfect tea every time?

Page 20: Why your build matters

Inputs & parameters

Boil kettle

Pour water into pot

Empty pot

Put tea in pot

Brew for n seconds

Fill kettle

What type of tea?

How much?

How long?

How much water?

Build inputs: * water quality * tea bags/loose leaf * tea type Build parameters: * quantity of tea * quantity of water * brew time

Page 21: Why your build matters

Same inputs should result in same outputs

In other words, repeatable builds Environment should not be a factor - boiling kettle at top of mountain reduces temperature of water Consider “works for me” with local Maven cache

Page 22: Why your build matters

How many cups of tea do you need to make a day?

Speed and efficiency are useful

Page 23: Why your build matters

Task avoidance

Boil kettle

Pour water into pot

Empty pot

Put tea in pot

Brew for n seconds

Fill kettle What if pot already

empty?

What if hot water already

available?

Doing all steps slows you down if some are unnecessary Output of “boil kettle” is hot water If hot water already exists, no need to boil

Page 24: Why your build matters

Incremental build (up-to-date checks) saves time

Often quicker to check whether a task is up to date rather than run it regardless

Page 25: Why your build matters

A build should be…

Automated

Repeatable

As fast as possible

Page 26: Why your build matters

The

Way

Page 27: Why your build matters

Automation

Page 28: Why your build matters

Custom build

publishPdf

publishGuide

apiDocs

fetchGrailsSource

grails-doc project

expensive, so optional

A build to generate the Grails user guide Automation of all steps No standard tasks Task graph is a suitable model

Page 29: Why your build matters

Custom tasks in Gradle

task publishGuide << { // Generate HTML from .gdoc }

buildSrc/src/groovy/pkg/PublishGuideTask.groovy

pkg.PublishGuideTask in a JAR

Page 30: Why your build matters

Optional task

apiDocs.onlyIf { !System.getProperty("disable.groovydocs") }

Page 31: Why your build matters

Add custom tasks to standard Groovy/Java projects

Even “standard” builds often have custom steps, e.g. integration tests deployment documentation Stop using scripts or other tools separate from the build!

Page 32: Why your build matters

Repeatable builds

Page 33: Why your build matters

Depends on tasks

Are tasks environment-dependent? Do system properties or environment variables have an effect? What about number of cores? Order of tests? Gradle can’t help much with this - except for task ordering

Page 34: Why your build matters

Task ordering

• mustRunAfter/shouldRunAfter

• No task dependencies

task integTest { ... } !task packageReports { mustRunAfter integTest ... }

`packageReports` does not depend on `integTest` But if both are executed, `packageReports` must come after `integTest` shouldRunAfter is less strict - mostly to optimise feedback

Page 35: Why your build matters

Task ordering

• finalizedBy

• Ensures execution of the finalizer task

task integTest { finalizedBy packageReports ... } !task packageReports { ... }

`packageReports` does not depend on `integTest` If `integTest` runs, then `packageReports` will run too, always after `integTest`

Page 36: Why your build matters

Dependency issues

Maven cache pollution (“works for me” syndrome) Different remote repository configurations (where is the dependency coming from?) Version resolution, eviction, and failed eviction Multiple JARs on the classpath and order counts

Page 37: Why your build matters

Gradle cache

• Origin checks

• Artifact checksums

• Concurrency safe

• Avoid mavenLocal()!

Artifacts are stored with source repo URL (allows origin checks) Artifact checksums protect against different binaries with same name & version Doesn’t solve the version conflicts issue

Page 38: Why your build matters

Resolution strategy

configurations.all { resolutionStrategy { failOnVersionConflict() ! force 'asm:asm-all:3.3.1', 'commons-io:commons-io:1.4' }

Override version to use for specific dependencies

Fail the build if any version conflicts

Defaults to ‘newest’ strategy

Fine-grained control over dependency versions Automatic conflict resolution error-prone

Page 39: Why your build matters

Resolution strategy

configurations.all { eachDependency { details -> if (details.requested.name == 'groovy-all') { details.useTarget( group: details.requested.group, name: 'groovy', version: details.requested.version) } } } Control modules with

different names, same classes

A painful problem to debug

Page 40: Why your build matters

Debugging dependencies

configurations.all.files.each { File f -> println f.name }

gradle dependencies

gradle dependencyInsight --dependency ...

`dependencies` gives you all dependencies in your project `dependencyInsight` shows why/how a given dependency is included in the project `<conf>.files` lists all the files and directories that will be on configuration’s classpath

Page 41: Why your build matters

Fast build execution

Page 42: Why your build matters

Depends on tasks

How long do individual tasks take? No parallel execution of tasks currently Can build decoupled projects in parallel

Page 43: Why your build matters

Incremental build

:lazybones-app:compileJava :lazybones-app:compileGroovy UP-TO-DATE :lazybones-app:processResources UP-TO-DATE :lazybones-app:classes UP-TO-DATE :lazybones-app:jar UP-TO-DATE :lazybones-app:startScripts UP-TO-DATE :lazybones-app:installApp UP-TO-DATE !BUILD SUCCESSFUL !Total time: 2.178 secs

UP-TO-DATE

Don’t execute tasks you don’t have to

Page 44: Why your build matters

Task inputs & outputs

class BintrayGenericUpload extends DefaultTask { @InputFile File artifactFile ! @Input String artifactUrlPath ! @Optional @Input String repositoryUrl …⋯ }

Use of annotations makes task support incremental build Inputs and outputs can be values, files, directories

Page 45: Why your build matters

Who needs to use the build?

What do they need to use it?

Will they use the whole build?

How long does it take?

Developers, QA, production types How much software needs to be set up before someone can use the build? How long is your wiki page?

Page 46: Why your build matters

User categories

compile

test

package/install

publishproject

lead only

Lazybones project

If the project is published, require credentials Fail fast Other users don’t require credentials

Page 47: Why your build matters

Interrogate task graph

gradle.taskGraph.whenReady { graph -> if (graph.hasTask(":lazybones-app:uploadDist")) { verifyProperty(project, 'repo.url') verifyProperty(project, 'repo.username') verifyProperty(project, 'repo.apiKey') ! uploadDist.repositoryUrl = project.'repo.url' uploadDist.username = project.'repo.username' uploadDist.apiKey = project.'repo.apiKey' } }

Only fail fast if `uploadTask` will be executed Fail slow would require integration tests to run - adding minutes to deployment

Page 48: Why your build matters

Other uses

• Include class obfuscation conditionally

• Limit visibility of tasks based on role

• Update version based on ‘release’ task

Page 49: Why your build matters

Finally…

Page 50: Why your build matters

A build should have…

• Features

• Error reporting

• A model of the process

Page 51: Why your build matters

Just like any other piece of software!

Page 52: Why your build matters

Summary

• End-to-end process is the build

• 80-20 rule (80% standard 20% custom)

• Automate everything == save time

• Invest in build as if it’s part of your code base

• Building software requires a rich model