Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Run build only if there are changes in src

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

like image 921
alecxe Avatar asked Jul 02 '15 18:07

alecxe


People also ask

How do you trigger a build only if changes happen on a particular set of files?

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.

What is used to track the changes between builds in Jenkins?

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.

What is changeset in Jenkins?

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" }

Which Jenkins command to get the list of changed files?

You can use the changeSets property of the currentBuild global variable to get information relating to the detected changes of the current build.


4 Answers

Here's an idea if you use git:

How about using something like grunt-gitinfo and using the last commit in HEAD as a base?

The idea is:

  • You create a new grunt task that checks for latest commit hash
  • You'd save this commit hash in a file that's added to gitignore (and is NOT in the clean folder, typically can be in root of repo)
  • Before saving to file, it'd check the value already in it (standard node fs module can do the read/write easily)
  • If the hash doesn't match, run build:prod task then save new commit hash
  • The testers build would depend on your new task instead of build:prod directly

Another option (still using git):

You 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.

like image 82
Meligy Avatar answered Oct 13 '22 02:10

Meligy


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
like image 43
J-Dizzle Avatar answered Oct 13 '22 03:10

J-Dizzle


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/'));
});
like image 2
allenhwkim Avatar answered Oct 13 '22 04:10

allenhwkim


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.

like image 2
Kasper Lewau Avatar answered Oct 13 '22 02:10

Kasper Lewau