DEV Community 👩‍💻👨‍💻

Andrés Vilina
Andrés Vilina

Posted on

How I write a gemspec.

Disclaimer: I made this article as a first attempt at writing; expect it to improve. Please don't doubt on commenting or asking for questions or improvements.

A gemspec is basically a gem's manifesto, it is a file describing what your gem is, does, and contains.

This is how I write a .gemspec file, and I'll explain you why.

# frozen_string_literal: true

require_relative 'lib/lumise/version'

Gem::Specification.new do |spec|
  spec.name    = 'lumise'
  spec.version = Lumise::Version

  spec.authors = ['vaporyhumo']
  spec.email   = ['roanvilina@gmail.com']

  spec.summary     = 'Some Summary'
  spec.description = 'Some Description'
  spec.license     = 'Unlicense'
  spec.homepage    = 'https://github.com/vaporyhumo/lumise'

  spec.metadata = {
    'homepage_uri'      => spec.homepage
    'bug_tracker_uri'   => spec.homepage + '/issues',
    'changelog_uri'     => spec.homepage + '/blob/master/CHANGELOG.md',
    'documentation_uri' => 'https://www.rubydoc.info/gems/lumise',
    'source_code_uri'   => spec.homepage
  }

  spec.bindir      = 'exe'
  spec.executables = ['lumise']
  spec.files       = Dir['lib/**/*'] # Dir['lib/**/{*,.*}']

  spec.required_ruby_version = '>= 2.4.0'

  spec.add_dependency 'thor', '~> 1.0'
end

First off, we declare Strings to be immutable within the file.

Basically every modern ruby file starts with this one.

# frozen_string_literal: true

Then we require the file that contains the gem's version.

I prefer using require_relative than adding lib to the $LOAD_PATH, which is something else you might find out there.

require_relative 'lib/lumise/version'

Said file would look something like this:

# frozen_string_literal: true

module Lumise
  VERSION = '0.1.0'
end

This is done mainly because you want a single authoritative source of truth for your gem's version. Update once, reflect everywhere.

Then we create the actual specifications, every gemspec needs to create this object, and specify all configurations within the given block.

Gem::Specification.new do |spec|
  ...
end

In the first chunk, we specify the gem's name, and reference the gem's version.

The name should be compliant with RubyGems recommendations ideally.

spec.name    = 'lumise'
spec.version = Lumise::Version

The constant Lumise::Version is available from the file required before.

Next, we add contact information, by providing a list of authors, and a list of emails.

spec.authors = ['vaporyhumo']
spec.email   = ['roanvilina@gmail.com']

Then we describe our gem a little bit by providing: a short summary, a longer description, the license for using, modifying or sharing the gem, and a homepage, that should point to the gem's git repository if nothing else.

spec.summary     = 'Some Summary'
spec.description = 'Some Description'
spec.license     = 'Unlicense'
spec.homepage    = 'https://github.com/vaporyhumo/lumise'

Later, we provide some metadata, that are actually useful links that will be displayed in the RubyGems page of your gem.

spec.metadata = {
  'homepage_uri'      => spec.homepage
  'bug_tracker_uri'   => spec.homepage + '/issues',
  'changelog_uri'     => spec.homepage + '/blob/master/CHANGELOG.md',
  'documentation_uri' => 'https://www.rubydoc.info/gems/lumise',
  'source_code_uri'   => spec.homepage
}

We can use the already defined spec.homepage variable here!~

Following this, we will specify which files should be packed when building
the gem, and which executables should be installed with it.

spec.bindir      = 'exe'
spec.executables = ['lumise']
spec.files       = Dir['lib/**/*'] # Dir['lib/**/{*,.*}']

Specifying the bindir is particularly important, since by default it will point to the bin directory, which you might use for scripts meant to help us in the development cycle of the gem, but are not meant to be consumed by the final user. Usually exe is used instead.

When using bundler to create your gem, the bin directory will hold the
setup and console scripts, so remember to change it!

The executables variable will hold an array with the filenames of the scripts that you do want the gem to provide; these live under the formerly specified bindir directory.

You must manually specify all of the files that should be included in the
built gem; any kind of array with file names will do, I usually just use Dir to include all of the lib directory and nothing else. You can find the rest on the repo.

Some people use git ls-files and then filter the results...

I find this unnecessary, when probably you just need files under lib.

Going forward, we should specify what versions of ruby the gem will work with.

I like to keep this one (1) minor below the last supported version, so people upgrading can still use the gem, though I think I wouldn't mind dropping that support if by any chance I needed to.

spec.required_ruby_version = '>= 2.4.0'

Using a CI to check for this compatibility is a good idea, since checking
manually can be a hassle.

Lastly, we add our gem's dependencies. Always specifying the required versions.

spec.add_dependency 'thor', '~> 1.0'

Of course, you would skip this step if no dependencies are needed.

Some people like to also add development dependencies in the gemspec; I don't.

I put all my development dependencies in the Gemfile, mostly because they have a lot to do with my development cycle and little to nothing to do about the actual gem.

Top comments (2)

Collapse
 
osnerrebete profile image
Osner Rebete

It's very clear <3

Collapse
 
silva96 profile image
Benjamín Silva

Very useful @vaporyhumo Thanks!

Timeless DEV post...

Git Concepts I Wish I Knew Years Ago

The most used technology by developers is not Javascript.

It's not Python or HTML.

It hardly even gets mentioned in interviews or listed as a pre-requisite for jobs.

I'm talking about Git and version control of course.