Snapping a Ruby app. Does the (v1) Ruby plugin still work?

Hi

I’m planning to deploy our Ruby server apps as Snaps in the future. As a first step, I’m just trying to snap the Rubygem hello-world, using the Ruby plugin (v1). Using core18 as core20 is not supported the Ruby plugin (beta).

I’m encountering a lot of issues. Using use-bundler: true it can’t find the Gemfile even though it’s in the root of my test project. If I override-build to bundle install --gemfile ../src/Gemfile myself, that step works. But then snapcraft build fails with:

Building my-snap-test
+ /snap/bin/snapcraft build
Traceback (most recent call last):
  File "/snap/snapcraft/6350/bin/snapcraft", line 10, in <module>
    from importlib.metadata import distribution
ModuleNotFoundError: No module named 'importlib.metadata'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/snap/snapcraft/6350/bin/snapcraft", line 13, in <module>
    from importlib_metadata import distribution
ModuleNotFoundError: No module named 'importlib_metadata'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/snap/snapcraft/6350/bin/snapcraft", line 15, in <module>
    from pkg_resources import load_entry_point
ModuleNotFoundError: No module named 'pkg_resources'
Failed to run 'override-build': Exit code was 1.

What I also noticed is that I can’t seem to just edit my snapcraft.yml and rerun snapcraft because Ruby would fail to find the optparse gem. So I do snapcraft clean; snapcraft --debug a lot, which takes ages.

Does anyone know if the Ruby plugin still works at all?
Would it make sense for me to try to contribute a V2 Ruby plugin?

Thanks.

If you are an experienced ruby developer, then yes, a v2 plugin would be appreciated.

Do you have a reproducer project to look into the issue at hand when using core18 as a base?

I’ve been trying with a very simple snapcraft.yaml:

name: my-snap-test
base: core18
version: '0.1.0'
summary: Ruby gem test snap
description: |
  Test snap for running executable from a Rubygem.

grade: devel
confinement: devmode

parts:
  my-snap-test:
    plugin: ruby
    # use-bundler: true
    source: .
    override-build: |
      /snap/bin/snapcraft build
      bundle install --gemfile ../src/Gemfile

apps:
  my-snap-test:
    command: bin/hello-world

And this Gemfile:

source 'https://rubygems.org'
gem 'hello-world'

I moved the snapcraft.yaml file out of the snap directory so it is in the root of the test project next to the Gemfile. Correct me if I’m wrong, but that seems to be the classic layout that is expected by V1 plugins.

Running snapcraft clean && snapcraft leads to:

[...]
installing extension scripts: //lib/ruby/2.4.0
installing extension scripts: //lib/ruby/site_ruby/2.4.0
installing extension scripts: //lib/ruby/vendor_ruby/2.4.0
installing extension headers: //include/ruby-2.4.0/ruby
installing default gems:      //lib/ruby/gems/2.4.0 (build_info, cache, doc, extensions, gems, specifications)
                              bigdecimal 1.3.0
                              io-console 0.4.6
                              json 2.0.4
                              openssl 2.0.5
                              psych 2.2.2
                              rdoc 5.0.0
installing bundle gems:       //lib/ruby/gems/2.4.0 (build_info, cache, doc, extensions, gems, specifications)
                              test-unit 3.2.3
                              xmlrpc 0.2.1
                              power_assert 0.4.1
                              rake 12.0.0
                              did_you_mean 1.1.0
                              minitest 5.10.1
                              net-telnet 0.1.1
Building my-snap-test
+ /snap/bin/snapcraft build
Traceback (most recent call last):
  File "/snap/snapcraft/6350/bin/snapcraft", line 10, in <module>
    from importlib.metadata import distribution
ModuleNotFoundError: No module named 'importlib.metadata'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/snap/snapcraft/6350/bin/snapcraft", line 13, in <module>
    from importlib_metadata import distribution
ModuleNotFoundError: No module named 'importlib_metadata'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/snap/snapcraft/6350/bin/snapcraft", line 15, in <module>
    from pkg_resources import load_entry_point
ModuleNotFoundError: No module named 'pkg_resources'
Failed to run 'override-build': Exit code was 1.

So the problem was that under override-build I used snapcraft build instead of snapcraftctl build.

I’ve switched from the ruby v1 plugin to the ruby snap as a build-snaps and stage-snaps dependency. This works with all bases and cuts down my build time, as ruby does not need to be built from source each time. The armhf releases of the ruby snap lag a bit behind though, but its maintainer on GitHub is usually quick to respond :slight_smile:

Only keep in mind that you need to invoke ruby.bundle in your override-build to ensure bundler uses the proper environment. Rewriting the shebang line on your ruby CLI scripts helps, too:

# Replace build-snap ruby path with our snap path to ensure ruby scripts call the correct binary
      find <path-in-your-snap>/ -type f -exec grep -Iq . {} \; -and -exec sed -i -e 's|^#!/snap/ruby/current/bin/ruby$|#!/snap/<your-snap-name>/current/bin/ruby|' {} \;

Note: one major downside of this approach is that some gem’s native extensions will fail to build if the Ruby snap was built with a different GLIBC version than what your snap’s build env has to offer. As of 2022-01-10, the published armhf revisions of the Ruby snap are slightly outdated and still built from core18, whereas the amd64 revsions are on core20.