Github actions compilation/caching issues

Hey folks!

Been through it all with elixir compilation. We were using Concourse for a bit, which allowed me to run images that had our application pre-compiled. I would rsync the files into the cloned branch, and only the changed files would compile.

For many reasons we moved to github actions, and since then I have been unable to create a setup that doesn’t compile/get dependencies every time. We also have issues with branches that change our structure.sql file, as the test runs depend on seeding a database, and new relations tend to not be applied unless we we “bust the cache” by incrementing a version number in the build cache string.

This all has created the headache of using up our minutes quite rapidly as our team (or a pull request) grows, and dipping deeply into our actions overage budget.

Below is part of a sample action

jobs:
  ingest:
    runs-on: ubuntu-18.04
    services:
      redis:
        image: redis
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 6379:6379
      postgres:
        image: postgres:11.5
        env:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: postgres
        ports:
          - 5432:5432
        options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5

    steps:
    - uses: actions/checkout@v2

    - name: Setup elixir
      uses: erlef/setup-elixir@v1
      with:
        elixir-version: ${{ env.ELIXIR_VERSION }}
        otp-version: ${{ env.OTP_VERSION }}

    - name: Retrieve Mix Dependencies Cache
      uses: actions/cache@v2
      id: mix-cache
      with:
        path: backend/deps
        key: ${{ env.OTP_VERSION }}-${{ env.ELIXIR_VERSION }}-test-deps-cache-{{ hashFiles('**/mix.lock') }}
        restore-keys: |
          ${{ env.OTP_VERSION }}-${{ env.ELIXIR_VERSION }}-test-deps-cache-
    - name: Retrieve Build Cache
      uses: actions/cache@v2
      id: build-cache
      with:
        path: backend/_build/**
        key: ${{ env.OTP_VERSION }}-${{ env.ELIXIR_VERSION }}-app-build-cache-v5-${{ hashFiles('**/mix.lock','**/structure.sql') }}
        restore-keys: |
          ${{ env.OTP_VERSION }}-${{ env.ELIXIR_VERSION }}-app-build-cache-v5-
    - name: Install Mix Dependencies
      run: |
        cd backend/
        mix local.rebar --force
        mix local.hex --force
        MIX_ENV=test mix deps.get
    - name: Compile Dependencies
      if: steps.build-cache.outputs.cache-hit != 'true'
      run: |
        cd backend
        MIX_ENV=test mix compile
    - name: Run Tests
      run: |
        cd backend/
        MIX_ENV=test mix ecto.reset
        mix cmd --app app mix test --color

Other examples I’ve found around the forums don’t seem to improve anything.

Does anything stand out here? How can I make these not get deps/compile on every run?

Finally, we also split our tests up so that each have their own actions file. If a PR is open and X app has files changed, the related action runs. If multiple apps have changes, those tests run. Our main package, all of the tests run. Is there possibly a way for a PR to not run all of these tests if a subsequent commit to that PR doesn’t change the apps that have already been tested?

Happy to share more details/clarify, apologies for any longwindedness or confusion.

1 Like

Just as a note, we have 35 test actions / 24 apps in an umbrella, hence the complicated actions setup. Perhaps a root of some of our problems.

Probably try replacing that with uses: erlef/setup-beam@v1? That’s the only thing that kind of stands out to me because in our projects that’s what we use for our GitHub actions setup.

1 Like

Thanks for your input, we still end up compiling our applications on every run.

1 Like

The following combination of steps ensures there is no recompilation happening in my case

 - name: Set up Elixir
        uses: erlef/setup-beam@v1
        with:
          elixir-version: ${{ matrix.elixir }} # Define the elixir version [required]
          otp-version: ${{ matrix.otp }} # Define the OTP version [required]

- name: Restore elixir dependencies cache
        id: mix-cache
        uses: actions/cache@v2
        with:
          path: deps
          key: ${{ runner.os }}-mix-${{ matrix.otp }}-${{ matrix.elixir }}-${{ hashFiles('**/mix.lock') }}

 - name: Restore build cache
        id: build-cache
        uses: actions/cache@v1
        with:
          path: _build
          key: cache-${{ runner.os }}-dialyzer_build-${{ matrix.otp }}-${{ matrix.elixir }}-${{ hashFiles('**/mix.lock') }}

      - name: Install Mix Dependencies
        if: steps.mix-cache.outputs.cache-hit != 'true'
        run: |
          mix local.rebar --force
          mix local.hex --force
          mix deps.get

      - name: Compile
        if: steps.build-cache.outputs.cache-hit != 'true'
        env:
          MIX_ENV: test
        run: mix deps.compile; mix compile --force --warnings-as-errors

I also had the two caches combined into one in the past, which worked just as well.

Note that I also do a --warnings-as-errors here for an additional check.

The differences to your setup that I’m seeing are,

  • no structure.sql in the hash
  • i use matrix, not env for versioning
  • my build path is just _build, not _build/**
  • all the critical steps run in test env using the env option in the config
2 Likes