The story:
We have a team of testers working on automating end-to-end tests using protractor for our internal AngularJS application. Here is the task they usually run for "local" testing:
grunt.registerTask('e2e:local', [
'build:prod',
'connect:test',
'protractor:local'
]);
It runs the "build" task, starts a webserver and runs the e2e tests against the local build.
The build:prod
task itself is defined as:
grunt.registerTask(
'build:prod', [
'clean',
'copy:all',
'copy:assets',
'wiredep',
'ngtemplates',
'useminPrepare',
'concat',
'ngAnnotate',
'autoprefixer',
'uglify',
'cssmin',
'copy:cssfix',
'usemin',
'copy:html',
'bowercopy',
'template:setProdVersion'
]
);
Here we have a lot of subtasks (it definitely could be improved, but this is how it looks now).
The problem:
Currently, it takes about 25 seconds for the build to complete. And, every time a person is running end-to-end tests, the build task is executed.
The question:
How can I run the build:prod
task only if there are changes in src
directory?
Note that the requirement here is to make it transparent for the testers who run the tests. I don't want them to remember when they need to perform a build and when not.
In other words, the process should be automated. The goal is to automatically detect if build is needed or not.
Note that ideally I would like to leave the build task as is, so that if it is invoked directly via grunt build:prod
it would rebuild regardless of the datestamp of the previous build.
Thoughts and tries:
there is the closely related grunt-newer
package, but, since we have a rather complicated build, having a clean
task at the beginning, I'm not sure how to apply it in my case
what I was also thinking about is to, inside the e2e:local
task, manually check the timestamps of the files inside dist
and src
and, based on that, decide if build:prod
is needed to be invoked. I think this is what grunt-newer
is doing internally
we started to use jit-grunt
that helped to improve the performance
If you are using a declarative syntax of Jenkinsfile to describe your building pipeline, you can use changeset condition to limit stage execution only to the case when specific files are changed. This is now a standard feature of Jenkins and does not require any additional configruation/software.
The simplest way to know what has changed on your Jenkins builds! Last Changes is a Jenkin plugin that shows rich VCS diffs between builds.
This is what the Jenkins docs say about the "changeset" directive: changeset. Execute the stage if the build's SCM changeset contains one or more files matching the given string or glob. Example: when { changeset "**/*.js" }
You can use the changeSets property of the currentBuild global variable to get information relating to the detected changes of the current build.
How about using something like grunt-gitinfo and using the last commit in HEAD as a base?
The idea is:
gitignore
(and is NOT in the clean
folder, typically can be in root of repo)fs
module can do the read/write easily)build:prod
task then save new commit hashbuild:prod
directlyYou can use something like grunt-githooks and create a git hook that runs after pull and calls the git build:prod
, then you can remove it from the dependencies of the grunt task that testers run.
You might have another code to check for githook and install it if required though, which can be a one-time extra step for testers, or maybe baked into the grunt task they call.
I'm surprised noone has mentioned grunt-contrib-watch yet (it's in the gruntjs.com example file and I thought it was pretty commonly known!). From github: "Run predefined tasks whenever watched file patterns are added, changed or deleted." - heres a sample grunt file that would run your tasks any time any .js files are modified in src/ or in test/, or if the Gruntfile is modified.
var filesToWatch = ['Gruntfile.js', 'src/**/*.js', 'test/**/*.js'];
grunt.initConfig({
watch: {
files: filesToWatch,
tasks: ['build:prod',
'connect:test',
'protractor:local']
}
});
grunt.loadNpmTasks('grunt-contrib-watch');
You have your developers open a terminal and run grunt watch
before they start modifying files, and every time those files are modified the tasks will automatically be run (no more going back to the terminal to run grunt build:prod
every time).
It's an excellent package and I suggest you check it out. -- github -- npmjs.org
npm install grunt-contrib-watch --save-dev
Not the answer your are looking for with grunt
, but this will be easy with gulp
.
var fs = require('fs');
var gulpif = require('gulp-if');
var sourceChanged = fs.statSync('build/directory').mtime > fs.statSync('source/directory').mtime;
gulp.task('build:prod', function() {
if (!sourceChanged) {
return false;
}
return gulp.src('./src/*.js')
.pipe(.... build ....)
.pipe(gulp.dest('./dist/'));
});
Here's how we've done some Git HEAD sha work for our build. We use it to determine which version is currently deployed to our production environment - but I'm quite certain you could rework it to return a boolean and trigger the build if truthy.
Gruntfile.js
function getHeadSha() {
var curr, match, next = 'HEAD';
var repoDir = process.env.GIT_REPO_DIR || path.join(__dirname, '..');
try {
do {
curr = grunt.file.read(path.join(repoDir, '.git', next)).trim();
match = curr.match(/^ref: (.+)$/);
next = match && match[1];
} while (next);
} catch(ex) {
curr = 'not-found';
}
return curr;
}
grunt.initConfig({
replace: {
applicationVersion: {
src: '<%= config.dist %>/index.html',
overwrite: true,
replacements: [{
from: '{{APPLICATION_VERSION}}',
to: getHeadSha
}]
}
}
});
grunt.registerTask('build', {
'replace:applicationVersion',
/** other tasks **/
});
grunt.registerTask('e2e:local', {
'check_if_we_should_build',
/** other tasks **/
});
index.html
<html data-version="{{APPLICATION_VERSION}}">
<!-- -->
</html>
There's also the git-info package which would simplify this whole process, we're looking at switching over to that ourselves.
edit; I just noticed @meligy already pointed you in the direction of git-info. credit where credit is due.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With