Build Automation
by Peter Ajtai
a Lead Dev at:
Fat fingers
Distracted minds
Mystery code with no history
Solution:
Build Automation
History
- Originally for compilers / linkers
- XML / Shell
- Now there are more options. Your imagination is the limit.
Advantages
- Defined build options producing defined outputs
- Don't have to remember to manually update variables / config files
- Don't have to remember to manually move files
- Don't have to remember to manually package, commit, etc.
- Reliability
- Reproducibility
- Clients sleep easier at night
Grunt
-
Grunt is not only for Javascript
- We all know Javascript
- Grunt uses Node (server JS)
- Writing your own tasks is simple
- Kicking off other - non Grunt - tasks is simple
- Grunt has a vibrant community of plugin authors
- SOLID has some plugins
- This means they can be used in multiple projects, but updated centrally
- NPM means have access to old versions
Our build goals
Builds to push to stage and production
No CI (yet?)
Anatomy of a Grunt process
- config object - in grunt.js
- package.json - your dependencies
- task registrations and definitions
- let's look at an example...
Proof of concept that we can incorporate automated builds
- Minimum necessary tasks for a stage push
- If it works (and it does!), much more can be added
The Tasks
grunt.registerTask('shell:cleanStage cp:temp setPHPConstant:stage' +
'useref concat min cssmin shell:propelGen' +
'clean cp:stage shell:buildStageToGitlab');
// set things up
shell:cleanStage cp:temp
// PHP environment changes - leverages PHP
setPHPConstant:stage
// min, concat, cache bust (for twig templates too)
useref concat min cssmin
// propel
shell:propelGen
// cleanup
clean cp:stage
// git going - this task is more involved
shell:buildStageToGitlab
// set things up
shell:cleanStage cp:temp
- Tasks can use node directly, but often simpler to use a Grunt task
// PHP environment changes - leverages PHP
setPHPConstant:stage
setPHPConstant : {
stage : {
constant : 'ENV',
value : 'staging',
file : project.dirs.temp + project.files.constants
}
}
// Changes this
define('ENV', 'local');
// To this, and PHP does the rest...
define('ENV', 'staging');
-
php-set-constant is a Grunt plugin on NPM
- Inter office collaboration. Thanks Eric!
- International collaboration. Thanks Róbert!
- Integrates with how you naturally do things in PHP to set environments
// min, concat, cache bust (for twig templates too)
useref concat min cssmin
-
useref: also one of our Grunt plugins
- A more complex task; makes use of other tasks
- useref could call concat, min, and cssmin directly, but users need flexibility as to order of calls. Order is left up to user.
- What useref does
- Allows you to define what to minify and concatenate and how to cache bust all in one place
- All these things are defined in build blocks...
Build blocks
- Concept from H5BP
and Yeoman
-
useref simplifies and customizes the concept to our needs (many pages with blocks, grunt templates, comments within blocks, options for cache bust, etc.)
- Works for both js and css
- As many blocks on a page as you want
- As many pages with blocks you want
- Don't have to manually update a list of minification or concatenations!
- Can easilly run project for debugging without min, concat, etc.
Build blocks
<!-- build:js /js/base.<%= grunt.template.today('yymmddhhMM') %>.min.js -->
<script type="text/javascript" src="/js/vendor/jquery.1.7.1.js"></script>
<script type="text/javascript" src="/js/vendor/bootstrap.2.2.2.js"></script>
<!-- Underscore drop-in -->
<script type="text/javascript" src="/js/vendor/lodash.0.8.0.js"></script>
<script type="text/javascript" src="/js/vendor/cryptojs-3-0-2/rollups/hmac-sha1.js"></script>
<script type="text/javascript" src="/js/vendor/cryptojs-3-0-2/components/enc-base64-min.js"></script>
<script type="text/javascript" src="/js/vendor/modernizr-2.5.3.js"></script>
<!-- endbuild -->
<!-- The above compacts to this -->
<script src="/js/base.1301170537.min.js"></script>
<!-- AND it creates the min concat file, and puts it in the appropriate spot -->
// propel
shell:propelGen
- Just another task that you would always have to remember
// cleanup
clean cp:stage
- More things you'd have to remember
- clean
- Removes things like schemas, config files, etc.
-
clean : {
buildProperties : project.dirs.temp + project.files.buildProperties,
runtimeConf : project.dirs.temp + project.files.runtimeConf,
schema : project.dirs.temp + project.files.schema
},
- If you refer to a multi task without a target, all targets are run.
- e.g "clean" vs "clean:schema"
- At this point everything is in targets/stage - this is .gitignored!
// git going - this task is more involved
shell:buildStageToGitlab
buildStageToGitlab: {
command: 'git stash save "Build script saving current branch state" ' +
'&& git checkout stage ' +
'&& git pull --rebase ' +
'&& ls | grep -v ^targets$ | grep -v ^node_modules$ | grep -v ^cache$ | grep -v ^logs$ | grep -v ^temp$ | xargs rm -r ' +
'&& cp -R targets/stage/ . ' +
'&& echo $(($(<.build) + 1))>.build ' +
'&& git add -A ' +
'&& git commit -am "Stage build: "$(<.build)' +
'&& git push origin stage ' +
'&& git checkout - ' +
'&& git stash pop',
stdout : true,
stderr : true
}
},
buildStageToGitlab
git stash save "Build script saving current branch state"
- Since this is for staging, you don't need everything committed
- Production would be different
- This needs to be cleaned up, since it shows error if everything IS commited (but works anyway)
buildStageToGitlab
git checkout stage
- We can switch branches
- Grunt is global
- Grunt config is already stored
- And node_modules is in .gitignore
- Untracked files are preserved on branch switches
- So what happens to targets/stage ?
buildStageToGitlab
git pull --rebase
- Make sure you have latest
- Make sure you rewind head, and apply your changes during the ff (no meaningless merges)
- --rebase should be in your ~/.gitconfig but we can't be sure
buildStageToGitlab
git pull --rebase
- Two people are at commit "Original"
- Person 1: Original - 1Change
- Person 2: Original - 2Change
- Person 1 pushes to remote
- At this point person 2 has diverged
- Solution rewind head to Original, add 1Change, now add 2Change and no need to merge
buildStageToGitlab
ls |
grep -v ^targets$ | grep -v ^node_modules$ | grep -v ^cache$ |
grep -v ^logs$ | grep -v ^temp$ |
xargs rm -r
- Grunts flexibility allows you to preserve the headaches of shell tasks... yay?
- We delete everything but certain chosen directories
- grep -v "inverts" your grep - everything but
- You can pipe arguments into xargs
- We get the args from a filtered "ls" command
- Basically delete the old stage build and start fresh
buildStageToGitlab
cp -R targets/stage/ .
- Copy new build to root of stage
buildStageToGitlab
echo $(($(<.build) + 1))>.build
- There is a file on stage that contains the build number
- This is public and useful for bug reports / etc.
- It is also a simple sanity check
- If we know a version worked, can revert to it with git
- The code pulls contents of .build into a variable
- adds one
- pushes updated number back to .build file
buildStageToGitlab
git add -A
- add all new files (if any) to git stage
buildStageToGitlab
git commit -am "Stage build: "$(<.build)
- Commit to stage with a message indicating the build number
- We wanted pushes to stage to have incremental build numbers no matter how many people commit in what order
- The .build file along with the git pull guarantees this
buildStageToGitlab
git push origin stage
buildStageToGitlab
git checkout -
buildStageToGitlab
git stash pop
- Apply your local changes and discard that stash
The bright new future
- Automation to make sure we don't forget things
- Flesh out this build process
- Unit tests - we already wrote them
- Linting JS, CSS, PHP - this is delicate
- CI and emails to interested parties
THE END
by Peter Ajtai
Thanks to: Hakim El Hattab / hakim.se for reveal.js