Build and release a gem
Version and gemspec
A gem needs a name, a version and a list of files. Keep the version in one place, a small file the gemspec reads, so the tag, the changelog and the package always agree.
lib/version.rb
module CiDemo
VERSION = "0.1.0"
end
ci-demo.gemspec
require_relative "lib/version"
Gem::Specification.new do |spec|
spec.name = "ci-demo"
spec.version = CiDemo::VERSION
spec.summary = "A tiny todo library used by the CI lessons"
spec.authors = ["Arthur Isaev"]
spec.license = "MIT"
spec.homepage = "https://www.aredel.com/dev/ruby/course/"
spec.files = ["lib/todo.rb", "lib/version.rb"]
spec.require_paths = ["lib"]
spec.required_ruby_version = ">= 3.0"
end
gem build
Build the package from the gemspec:
gem build ci-demo.gemspec
Successfully built RubyGem
Name: ci-demo
Version: 0.1.0
File: ci-demo-0.1.0.gem
The file lands next to the gemspec. Its name carries the version, so a new release is a new file and never an overwrite.
Install the package
Install the local file before pushing anywhere. A path with a slash or a dot is installed as a local package, which is the quickest end-to-end check:
gem install ./ci-demo-0.1.0.gem
Successfully installed ci-demo-0.1.0
Parsing documentation for ci-demo-0.1.0
Installing ri documentation for ci-demo-0.1.0
Done installing documentation for ci-demo after 0 seconds
1 gem installed
If the install succeeds but require "todo" fails, the files list in the gemspec is incomplete: the package was built without that file. This is the most common gemspec mistake, and the local install is where it shows up.
gem push
Publishing sends the package to RubyGems, where Bundler can find it:
gem push ci-demo-0.1.0.gem
Publishing needs a rubygems.org account and an API key; the command reads
the key from ~/.gem/credentials or from a secret
that the pipeline passes in. The example gem of this course was not pushed,
so there is no output to show for it: the command is here so you know
where the release ends.
Keep the credentials file out of the repository and pass the key as a
secret to the job.
In a pipeline, push only from a tag: build the package, check that the
version in the gemspec matches the tag name, and then push. A release
from a branch is a release nobody can reproduce.
What to watch for
The version lives in one file, and the git tag
repeats it. A mismatch
between the tag and the gemspec is easy to create by hand and hard to
notice later, so let the pipeline read
CiDemo::VERSION and compare it with the tag before
the push.
Build the package in CI rather than on a laptop. The `.gem` file is
reproducible from the repository only if nothing local leaks into the
file list, and a build on a fresh machine is the easiest way to prove it.
Next: Auditing dependencies
Article author: Arthur Isaev