Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature request: support caching for multiple Gemfiles #170

Closed
searls opened this issue Apr 22, 2021 · 12 comments
Closed

Feature request: support caching for multiple Gemfiles #170

searls opened this issue Apr 22, 2021 · 12 comments

Comments

@searls
Copy link

searls commented Apr 22, 2021

Most of my gems include an example application in example/ that a test script will exercise, and—in the case of gems that interact with Rails—are usually where all the heavyweight gems are, so that's what would benefit the most from caching.

Example:

Gemfile
Gemfile.lock
example/
  Gemfile
  Gemfile.lock

I'd really love a way to tell the action I've got more than one Gemfile that needs to be installed so that both can benefit from caching.

Thank you!

@eregon
Copy link
Member

eregon commented Apr 22, 2021

Would https://github.com/ruby/setup-ruby/blob/master/README.md#matrix-of-gemfiles work for your use-case?
Or do you somehow need to install both gemfiles in the same CI job?

@searls
Copy link
Author

searls commented Apr 22, 2021

In each case, the CI job needs both. The top-level Gemfile is actually a gemspec, and specifies the gem's own dependencies. The example repo depends on the gem via path: ".." and has its own (meatier, because Rails) dependencies

@eregon
Copy link
Member

eregon commented Apr 23, 2021

It sounds like the example/Gemfile is then a superset of Gemfile, so maybe specifying BUNDLE_GEMFILE: example/Gemfile at the job level and using that Gemfile for everything (BUNDLE_GEMFILE does that) would work?

Implementing caching for multiple bundle install in the same CI job seems high effort.
It could be done with a new input but it seems extermely messy to handle multiple gemfiles paths, etc (e.g. I don't think think inputs support arrays).
Or it could be done by separating this action into two: "setup ruby" and "cache & install gems" part, but quite some data is actually passed between them so it seems not so easy.
A third way I can imagine would be to make this action detect it was already run before and if it was with the same ruby-version then basically skip the "setup ruby" part. Might be hard to detect and possibly have tricky corner cases.

@searls
Copy link
Author

searls commented Apr 23, 2021

I understand that supporting multiple caches is nontrivial, but I don't think setting BUNDLE_GEMFILE to configure which one gets cached is a very durable solution, because it's used by Bundler's CLI to locate the Gemfile, so any scripts that use bundle will (1) fail if the path is specified relatively (e.g. example/Gemfile) or (2) if they are meant to be run against the root Gemfile (e.g. pwd in project root and one runs bundle exec rake). (Example of this blowing up here: https://github.com/testdouble/test_data/runs/2410973435?check_suite_focus=true )

@eregon
Copy link
Member

eregon commented Apr 23, 2021

Regarding the path, it should be possible to set an absolute path using (https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#github-context):

    env:
      BUNDLE_GEMFILE: ${{ github.workspace }}/example/Gemfile

It's the same variable on purpose, so all bundle commands will use that Gemfile.

(2) is a problem if the root Gemfile has extra gems (or needs different versions) compared to the other one, indeed.
You could unset BUNDLE_GEMFILE as a workaround but of course that will not cache the second Gemfile.

The only way to make https://github.com/testdouble/test_data/blob/master/script/test work and cache both bundles seems to install with the cache before running that script, and that requires lots of changes (#170 (comment)).

I think a good solution might be to split that script in two parts, and run each of those in separate jobs with BUNDLE_GEMFILE set.

@searls
Copy link
Author

searls commented Apr 23, 2021

I was trying to set env on the overall job and not on the setup-ruby action, so that got me a little closer to at least caching the right project, but it looks like if BUNDLE_GEMFILE is in a subdirectory (example/Gemfile) it will still persist the gems in the workspace root: Bundled gems are installed into ./vendor/bundle`, which the subdirectory's Rails project won't find'"

I appreciate your help working through this in this thread, but I think I'm going to give up on trying to cache this for now (my reason for opening this issue was honestly less about this particular project and more about half dozen of other gems I maintain with 1-to-many example apps beneath them)

Thank you @eregon <3

@searls searls closed this as completed Apr 23, 2021
@rperryng
Copy link

👋 I have a bit of a niche use-case, I'm writing a library for CI purposes intended to interact with Ruby repositories (as well as other languages). It will execute bundle via the @actions/exec package and depending on the gems/rake tasks detected, invoke specific ones.

This library has multiple ruby project fixtures within it for testing, all with their own Gemfiles to simulate different repository setups. The tests run the script with different pwd values for the different ruby fixtures.

To enable caching in GH actions, I used multiple uses: ruby/setup-ruby steps each configured with a different fixture's working-directory value (and ruby-version value, as it wouldn't pick up the .ruby-version file from the root of the project). This has sped up the build time significantly, thank you for this work!

What I'm wondering is, would there be any more elegant approach to this? For example supporting multiple working-directory paths like the npm-install action?

Thanks again for your time.

@eregon
Copy link
Member

eregon commented May 15, 2021

Multiple uses: ruby/setup-ruby seems best, I don't want to handle multiple values per input, that seems likely to lead to a lot of complexity pretty quickly (and it wouldn't work if anything else than working-directory is needed).

I think supporting multiple uses: ruby/setup-ruby might be relatively easy, if given the same ruby version, and it seems to somehow already work for you.

@coderberry
Copy link

@searls Did you ever find a good solution for this?

@searls
Copy link
Author

searls commented Mar 11, 2023

Sorry, I haven't! (But for lack of trying! I don't even remember this issue)

@eregon
Copy link
Member

eregon commented Mar 11, 2023

Multiple uses: ruby/setup-ruby should work for that purpose, it's already used by some people.
We just need to add a test for it: #445

@adrianthedev
Copy link

If this 👆 doesn't work for you, @coderberry I can muster something up using actions/cache and another copy/paste step.

I'm using this setup to cache the bundler deps.

  1. setting up ruby
  2. trying to restore the cache in vendor/bundle based on the Gemfile.lock file hash
  3. then, running bundle install
    - name: Set up Ruby
      uses: ruby/setup-ruby@v1
      with:
        bundler: default
        ruby-version: ${{ matrix.ruby }}

    - uses: actions/cache@v3
      with:
        path: vendor/bundle
        key: ${{ runner.os }}-test-gems-${{ hashFiles('**/Gemfile.lock') }}
        restore-keys: |
          ${{ runner.os }}-test-gems-${{ hashFiles('**/Gemfile.lock') }}

    - name: Bundle install
      run: |
        bundle config path vendor/bundle
        bundle install --jobs 4 --retry 3

I'm assuming you're running bundle install in multiple directories.
If so, you could have two caching steps that would use two caching directories vendor/bundle and vendor/second_bundle.
Then when you run bundle install, you'd set those directories as the vendor ones.

The new approach would look something like this (untested):

    - name: Set up Ruby
      uses: ruby/setup-ruby@v1
      with:
        bundler: default
        ruby-version: ${{ matrix.ruby }}

    - name: First Gemfile cache
      uses: actions/cache@v3
      with:
        path: vendor/bundle
        key: ${{ runner.os }}-test-gems-${{ hashFiles('Gemfile.lock') }}
        restore-keys: |
          ${{ runner.os }}-test-gems-${{ hashFiles('Gemfile.lock') }}

    - name: Second Gemfile cache
      uses: actions/cache@v3
      with:
        path: vendor/second_bundle
        key: ${{ runner.os }}-test-gems-${{ hashFiles('subdirectory/Gemfile.lock') }}
        restore-keys: |
          ${{ runner.os }}-test-gems-${{ hashFiles('subdirectory/Gemfile.lock') }}

    - name: First Bundle install
      run: |
        bundle config path vendor/bundle
        bundle install --jobs 4 --retry 3

    - name: Second Bundle install
      run: |
        bundle config path vendor/second_bundle
        bundle install --jobs 4 --retry 3

Now, the First Bundle install and Second Bundle install have their own vendor directories that have the cache restored by GitHub Actions (if it's available).

It's not the most kosher approach, but it's rather easy to implement and understand later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

5 participants