Dynamic branch name for git based gems - ruby

I am using bundler to deploy gems from an in house git repository into my rails app. I would like to have different branch names for different groups, however, this:
group :production, :release_candidate, :staging, :demo do
gem "my_inhouse_gem", '0.0.1', git: 'git#github.com:my_gem.git', branch: 'master'
end
group :development, :develop do
gem "my_inhouse_gem", '0.0.1', git: 'git#github.com:my_gem.git', branch: 'develop'
end
fails with
You cannot specify the same gem twice coming from different sources.
You specified that mygem (= 0.0.1) should come from
git#github.com:my_gem.git (at develop) and
git#github.com:my_gem.git (at master)
While the following:
group :production, :release_candidate, :staging, :demo do
my_gem = 'master'
end
group :development, :develop do
my_gem = "develop"
end
gem "my_inhouse_gem", '0.0.1', git: 'git#github.com:my_gem.git', branch: my_gem
Simply uses whichever group is printed last.
After reading up on it and finding this article: http://yehudakatz.com/2010/05/09/the-how-and-why-of-bundler-groups/ ,I realize this is because bundler will still execute the contents of every group, and then simply install the ones that match.
How can I have a dynamic branch name based on environment config in my Gemfile?

Bundler isn't designed to handle this situation the way that you'd like. It's intended to provide a consistent set of gems. Groups can control which gems are installed in what environments, but not to switch the version of a gem.
There may be a better way to accomplish what you want. If you need to develop against an experimental version of a gem, you're probably better off doing it in a branch of your project.
See also: https://github.com/bundler/bundler/issues/751#issuecomment-22113199

This is how I worked around the issue:
At the top of my Gemfile:
rails = ENV['RAILS_ENV'] || 'default'
And then:
if rails.match /develop/i or rails.match /ci/ or rails.match /test/ or rails.match /default/i
# if you wish to test locally against your branch
#in_house_version = 'your_branch'
in_house_version = 'develop'
else
in_house_version = 'master'
end

Related

Is it OK to use an environment variable as version to build a Ruby gem?

I want to ease the deployment of an internal gem to our private Github rubygems registry. Most of the time, within the gemspec file, the version is hardcoded or read from another file. Meaning to publish a new release once your code base ready, you have to create a pull request, modified the version, merge, then finally publish.
This workflow does not match the other services in our monorepo. For these last, once we want to release, we create a branch release/service-name/v1.2 that trigger a Github Deployment and a dedicated CI pipeline where we can retrieve the name of the version to release.
So for our gem, I was thinking to use an environment variable to set the version when calling gem build rubocop-xxxxx like this:
Gem::Specification.new do |spec|
spec.platform = Gem::Platform::RUBY
spec.name = 'rubocop-xxxxx'
spec.metadata = {
'github_repo' => 'ssh://github.com/xxxxx/monorepo',
'allowed_push_host' => 'https://rubygems.pkg.github.com/xxxxx',
'source_code_uri' => 'https://github.com/xxxxx/monorepo/tree/main/packages/common/rubocop'
}
spec.version = ENV['PACKAGE_RELEASE_VERSION'] || "0.0.0"
spec.platform = Gem::Platform::RUBY
spec.required_ruby_version = '>= 2.7'
spec.files = Dir[
'rubocop.yml',
'conf/**/*',
'*.gemspec',
'Gemfile',
]
spec.add_dependency('rubocop-performance', '~> 1.13.3')
spec.add_dependency('rubocop-rails', '~> 2.14.2')
spec.add_dependency('rubocop-rake', '~> 0.6.0')
spec.add_dependency('rubocop-rspec', '~> 2.9.0')
spec.add_dependency('rubocop-shopify', '~> 2.5.0')
end
The 0.0.0 fallback is here to avoid issue when generating the Gemfile.lock when running bundle install.
Is it OK to have such gemspec, or can I see later some issue?
I would not use an environment variable for setting the gem version.
If your goal is to test a release candidate of rubocop-xxxxx gem in one of your service before you finally release it, you can use the path option in the Gemfile instead of releasing each change.
Example
Gemfile:
gem "rubocop-xxxxx", path: <path-to-the-gem>
When the version of the gem works as expected, release it (with a hard-coded version number), remove the path option from the Gemfile again and specify the released version instead.
Example
Gemfile:
gem "rubocop-xxxxx", "~> 1.1"

How can I conditionally load a local gem in my Rails 4.2 Gemfile?

I am using a gem that is only distributed in binary form. As such, we have two versions (not gem version numbers, just two different binaries) that we must somehow load conditionally in our environments since we develop on OS X and deploy on Linux (AWS).
I have these gems extracted to <app_root>/vendor/gems like so:
chilkat-9.5.0.69-x86_64-darwin/
chilkat-9.5.0.69-x86_64-linux/
I realize that I can set a development group and a production group in Gemfile:
group :development do
gem 'chilkat', path: 'vendor/gems/chilkat-9.5.0.69-x86_64-darwin'
end
group :production do
gem 'chilkat', path: 'vendor/gems/chilkat-9.5.0.69-x86_64-linux'
end
But that fails:
[!] There was an error parsing `Gemfile`: You cannot specify the same gem twice coming from different sources.
You specified that chilkat (>= 0) should come from source at `vendor/gems/chilkat-9.5.0.69-x86_64-darwin` and source at `vendor/gems/chilkat-9.5.0.69-x86_64-linux`
. Bundler cannot continue.
What's more is that I can't get the Bundler to run correctly on the production one by itself, maybe because it can't recognize the platform:
Could not find chilkat-9.5.0.69 in any of the sources
I don't know much about gemspec files, but maybe the last line:
--- !ruby/object:Gem::Specification
name: chilkat
version: !ruby/object:Gem::Version
version: 9.5.0.69
platform: x86_64-linux
tells Bundler to skip it if it specifies a different platform than the one on which it's running.
Initial Solution
by user ulferts below.
if RUBY_PLATFORM =~ /darwin/
gem 'chilkat', path: 'vendor/gems/chilkat-9.5.0.69-x86_64-darwin'
elsif RUBY_PLATFORM =~ /x86_64-linux/
gem 'chilkat', path: 'vendor/gems/chilkat-9.5.0.69-x86_64-linux'
end
I had been trying to check Rails.env but I wasn't aware of the RUBY_PLATFORM constant.
However
Bundler appears to be deficient and lacks flexibility here. This fails upon attempting to deploy into production:
You are trying to install in deployment mode after changing
your Gemfile. Run `bundle install` elsewhere and add the
updated Gemfile.lock to version control.
So even though the conditional is there, it appears that the Gemfile.lock file is causing there to be some kind of a problem. This is really unfortunate, because I don't think this legitimate use is that much of an edge case.
In short, even with a conditional, you can not have the same gem listed in your Gemfile twice. Whether for a different source, different version, or both.
I tried something else: changing the name of the gem in production. I changed the directory name, the gem name, and the references in the gemspec file. The result:
You have added to the Gemfile:
* source: source at `vendor/gems/chilkatprod-9.5.0.69-x86_64-linux`
* chilkatprod
You have deleted from the Gemfile:
* source: source at `vendor/gems/chilkat-9.5.0.69-x86_64-darwin`
* chilkat
You have changed in the Gemfile:
* chilkat from `no specified source` to `source at
`vendor/gems/chilkat-9.5.0.69-x86_64-darwin``
So suddenly it looks like it doesn't even like the conditional now. I had a put a conditional in the code that did the require to facilitate this, but I can't even get there if the code can't be deployed.
The Gemfile is just another ruby file. If you can figure out the architecture you are on, you can simply wrap it in an if ... else e.g.
if architecture_is_os_x?
gem 'chilkat', path: 'vendor/gems/chilkat-9.5.0.69-x86_64-darwin'
else
gem 'chilkat', path: 'vendor/gems/chilkat-9.5.0.69-x86_64-linux'
end
One possibility to differentiate would be setting an env variable in production.
Maybe you can use Install_if?
See http://bundler.io/man/gemfile.5.html#INSTALL_IF

How to manage dependencies between my ruby projects?

This sounds like a basic question, but I can't find any answer to it on the Internet.
So I have a git ruby project database_models. It's a gem. I want 3 other project to use it. I've added a dependency on this project to those 3 projects like this:
gem "database_models", :git => "path", :branch => master
Now, I want a a develop branch of those 3 projects to use the develop branch of database_models, and I want a master branch of those 3 projects to use the master branch of database_models, so that my production environment is stable and independent of my development environment.
I can see 4 options of doing this, and I don't like any of them:
Deploy database_models to the server, and update those 3 projects to reference database_models using a path, instead of git
Git submodule
User different versions of database_models gem (1.1, 1.2, 1.3...). I would probably need my own gem server for that, right?
Write some code in a Gemfile that would choose the correct branch based on the environment where "bundle install" is run.
Usually you'd use Bundler with a local path. Your Gemfile points to the Git or Github repo using git or github:
gem 'spree', github: 'spree/spree', branch: 'master'
Note that includes the branch. You can make each of your projects use a different branch of your gem if you want to. You can make each of your projects use a different branch of your gem if you want to. You can also use groups to deploy different versions of your gem depending on the environment:
group :development, :test
gem 'spree', github: 'spree/spree', branch: 'bleedinedge'
end
group :staging, :production
gem 'spree', github: 'spree/spree', branch: 'master'
end
The above will work fine as long as you keep pushing to the Github. But thanks to local config, you can run the following on your command line:
bundle config local.database_modules ~/Projects/gems/spree
That will add a line to your ~/.bundle/config, so when you run bundle in your projects, it will pull it from your local repo.
User different versions of database_models gem (1.1, 1.2, 1.3...). I would probably need my own gem server for that, right?
I would do this and simply have my Gemfile select the correct gem via a tag. Here is an example:
git 'https://github.com/rails/rails.git', tag: 'v5.0.0'
So you can do the same while storing your custom gem on github (no gem server required).
Here is how to use tags with Git.
This should give you the flexibility you need.

Bundler's rake release with geminabox?

Is there a way to configure bundler so that when I do rake release it would submit the gem to my own gem server (a gem in a box instance) rather than to rubygems?
Ideally this configuration would be something I can omit from my git repository.
Rubygems is actually hard-coded into bundler and I've found only one way around it.
The following monkeypatch should get you what you want:
module Bundler
class GemHelper
protected
def rubygem_push(path)
if Pathname.new("~/.gem/nexus").expand_path.exist?
sh("gem nexus '#{path}'")
Bundler.ui.confirm "Pushed #{name} #{version} to https://<your-url-here>/."
else
raise "Your Nexus credentials aren't set. Run `gem nexus #{path}` to push your gem and set credentials."
end
end
end
end
The above is for Nexus instead of Geminabox, but the concept should apply to either.
As far as omitting it from git, I'm afraid we're out of luck. However, you can share this appropriately between projects so it will only have to be checked into one place rather than many. Hope this helps!
i made a dependency free gem for this which imitates the geminabox http post request and overwrites bundlers rake release with rake release:inabox
https://github.com/dfherr/geminabox-release
I managed to change the task which uploads the gem to rubygems, that's less intrusive then the solution provided by JohnIV, though the concept is the same.
Rake::Task['release:rubygem_push'].clear
namespace :release do
task :rubygem_push do
version = ModuleName::VERSION
name = 'module_name'
cmd = "gem nexus pkg/#{name}-#{version}.gem"
puts `#{cmd} 2>&1`
end
end
Add 'bundler_geminabox' to your Gemfile:
group :development do
gem 'bundler_geminabox'
end
Then, in your rakefile, instead of requiring 'bundler/gem_tasks':
require 'bundler_geminabox/gem_tasks'
You don't need to add any tasks to the rakefile; you will automatically get rake build, rake install, and rake release, the last of which uploads to the server listed in ~/.gem/geminabox. Otherwise, the behavior is the same as the equivalent tasks provided by bundler/gem_tasks.
Gem on Github: https://github.com/joshkrueger/bundler_geminabox

Gemfile - separating Production gems from Development gems

So I know that in a Gemfile I can do something like this:
group :development, :test do
gem 'gem1'
gem 'gem2'
end
What I am looking to accomplish is something like this:
group :production do
gem 'gem1'
gem 'gem2'
end
group :development, :test do
gem 'gem1', :path => '/Documents/Code/gem1/'
gem 'gem2', :path => '/Documents/Code/gem2/'
end
So our application uses 2 gems that we also develop locally. In order to improve time while developing on our local machines, we want to be able to point our dev environments to the local copies of the gems - this way it picks up all changes without needing to restart our rails server. Otherwise we would have to rebuild the gem, re-install the gem, and restart rails with every development change in the gem.
However, doing this gives me the following error:
You cannot specify the same gem twice coming from different sources. You specified that gem1 (>= 0) should come from an unspecfied source and source at /Documents/Code/gem1
I have tried even running something like bundle install --without production and I get the same error.
Does anyone know if it IS possible to do what I would like to do?
Thanks!
i think that there is a supported way to do it and some hacks to work around it.
supported way:
use bundle config with the local option as described here: http://bundler.io/v1.3/man/bundle-config.1.html
hacky way:
use env vars and execute bundler before using in production: http://www.cowboycoded.com/2010/08/10/using-2-sources-for-a-gem-in-different-environments-with-bundler/
there is a feature-request for this problem on github with several related issues and lots of comments: https://github.com/carlhuda/bundler/issues/396
the github issue phoet linked to is resolved, and is consistent with the supported way.
I dug around through the docs, you'll need to set the config variable and updated your gemfile to reference the branch as well.
e.g.
edit your Gemfile:
gem <gem_name>, git: <git_url>, branch: <branch>
on command line:
bundle config local.<gem_name> <local_path_to_gem>
I solved this by creating a new Gemfile that's identical to the original except for the target gem's source. Then, in config/boot.rb, I used:
require 'rails'
if Rails.env.development?
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../DevGemfile', __FILE__)
else
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../ProdGemfile', __FILE__)
end

Resources