Early success in a build

Hi Everyone!

Loving CircleCI 2.0 so far, but we’re noticing some extra build time when we want to complete tests at an early step and not continue performing steps (an early success).

Scenario: We have a monorepo and we build 10+ dockerfiles in parallel. However, if the directory with the dockerfile isn’t different than the one in master (i.e. git diff origin/master path/to/service is empty) we don’t have to test and can complete the test early. See the following circle.yaml

version: 2
defaultBuild: &defaultBuild
  working_directory: ~/monorepo
  docker:
    - image: circleci/node:7.10.0
machine:
  services:
    - docker
jobs:
  project1:
    <<: *defaultBuild
    steps:
      - checkout
      - run: cd libs/scripts ./check-for-difference project1
      - setup_remote_docker:
          reusable: true
      - run: cd libs/scripts ./test project1
      - run: cd libs/scripts ./build project1
  project2:
    <<: *defaultBuild
    steps:
      - checkout
      - run: cd libs/scripts ./check-for-difference project2
      - setup_remote_docker:
          reusable: true
      - run: cd libs/scripts ./test project2
      - run: cd libs/scripts ./build project2

We don’t want to unnecessarily run the three steps after the check-for-difference step. I think we could do this with build dependencies, but we might have to checkout multiple times (maybe not that bad of a cost?). Is there a way to “exit early with success”?

3 Likes

I have a similar workflow that builds multiple Docker images from a monorepo. The solution I’ve come up with is to wrap that logic in a shell script which exits 0 if either the image is built or its not built because the relevant files did not change based on git diff.

The approach was to create a logical_changes.sh which parses the git refs in CIRCLE_COMPARE_URL to determine the appropriate git diff ref1 ref2 --name-only command to run. Then, for each logical change, which equates to a need to build a Docker image, that script exports values like CI_IMAGE_A_CHANGED or CI_IMAGE_B_CHANGED.

FInally, I have shell scripts for each docker build which source logical_changes.sh, test the value of the relevant CI_IMAGE_*_CHANGED then either exit 0 early or run the docker build.

Each Docker build in my workflow ends up looking something like:

build_and_push_foo_release:
  working_directory: /root/project
  docker:
    - image: foo/circleci-builder
  steps:
    - attach_workspace:
        at: /root/project
    - setup_remote_docker:
        version: 17.05.0-ce
    - run:
        name: Build Foo Docker Image
        command: |
          cd /root/project/
          TAG=$CIRCLE_BRANCH-$CIRCLE_SHA1
          ci/build_foo_docker_image.sh ecom/ $TAG

So, in cases where no build and push is necessary, those builds take about 60-90 seconds to setup_remote_docker, then exit when the script recognizes it need not build an image.

However, like you I recognize that ideally we could short-circuit earlier potentially using a when condition on the steps in a build that could test the value of an environment variable.

1 Like

Thanks for the tip @trumant, that approach makes a lot of sense and would definitely bring down our build time. The downside seems to be we have to run setup_remote_docker, and we also don’t get to see/profile the different steps in the circleci ui. That said I think I’ll use it in the mean time-

I wonder if this is a feature the circleci team might consider adding, I like your idea of some kind of when query, though I’m not sure how much control logic is appropriate in the config file- or how control logic would best be represented in the config.

Naive example of how a basic early success could be implemented in the config:

build_and_push_foo_release:
  working_directory: /root/project
  docker:
    - image: foo/circleci-builder
  steps:
    - attach_workspace:
        at: /root/project
    - when:
        command: /root/project/ci/did_foo_change.sh
        action: early_success
    - setup_remote_docker:
        version: 17.05.0-ce
    - run:
        name: Build Foo Docker Image
        command: |
          cd /root/project/
          TAG=$CIRCLE_BRANCH-$CIRCLE_SHA1
          ci/build_foo_docker_image.sh ecom/ $TAG
1 Like

What if you setup conditional logic in the YAML + workflow jobs of something like this?

if [[ ((`git diff ref1 ref2...` > 0))]]; then 
        echo "pass the job and build the image in the next job which sets up remote_docker"
      else
        echo "fail the job and skip next step in workflow job"
      fi

Here is my actual code I use with conditional logic although it is only for relating to doing steps if the commits are on the master branch:


Plus a conditional job workflow like this:

I could use something like this for the linked repo which builds all 6 Docker images every time, so if you get it going I’d like to check out the code.

1 Like

I’ve just come across this post now, and i’m curious whether we’re any closer to having monorepos working nicely with circle ci?

I can’t seem to find any proper answers regarding building only the projects that change. Early success would be nice.

Or a way to generate dynamic workflows? i.e. if conditions are met (git diff /root /project), create the workflow and run it, else skip over it?

1 Like

Another use case for this:

master branch runs only test job
staging & production branches run test & deploy jobs

staging & production runs should be able to detect that the same commit already produced a successful test job run in master and so it should just run deploy

I would like to see this feature too. My company already has some code to figure out whether to skip a build if current branch has no changes related to this build. I just need a way to tell Circle CI to skip the rest of the build after this step yield a flag or something like that.

1 Like

I’m not aware of a native skip feature, but would the git diff approach above be OK for you? That doesn’t skip the step from a CircleCI recording perspective, but you can elect not to run your tests based on whether there is a change in a repo sub-branch.

Although build servers cannot store state information, you could use the cache system to record the last known hash of a repo, which will help you determine whether to do tests or to skip them, perhaps in a shell script.

I think that approach was to:

  • Write a *.sh file with environment variable indicating we need to skip this build or not
  • Source that file in the next step so now the environment variables are set
  • Check environment variables before running the expensive build step

I agree this approach would work. Well, I still prefer to have a native feature where under certain circumstances, Circle CI just skip the rest of the build and mark this build successful.

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.