palkan / engems

Rails component-based architecture on top of engines and gems (showroom)
MIT License
136 stars 8 forks source link

Component method in main Gemfile generates error when running 'bundle install' #3

Open maiksimov opened 3 years ago

maiksimov commented 3 years ago

I am trying to attempt your code into my project. I added component and eval_gemfile_patch scripts the lib folder of my main application. I have an admin_panel engine that is included in the main Gemfile as written in the guide:

component 'admin_panel'

Engine Gemfile looks like:

source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gemspec
eval_gemfile './Gemfile.runtime'

Engine admin_panel.gemspec looks like:

$LOAD_PATH.push File.expand_path('lib', __dir__)
require 'admin_panel/version'

Gem::Specification.new do |spec|
  spec.name        = 'admin_panel'
  spec.version     = AdminPanel::VERSION
  spec.summary     = 'AdminPanel.'
  spec.description = 'AdminPanel provides functionality to manage project entities'

  spec.add_dependency 'activeadmin'
  spec.add_dependency 'devise'
  spec.add_dependency 'draper'
  spec.add_dependency 'dry-validation', '~> 1.5'
  spec.add_dependency 'omniauth', '~> 1.9.1'
  spec.add_dependency 'omniauth-google-oauth2'
  spec.add_dependency 'omniauth-rails_csrf_protection'
  spec.add_dependency 'rails', '~> 6.0.3', '>= 6.0.3.2'
  spec.add_dependency 'reform', '~> 2.3'
  spec.add_dependency 'sassc-rails'
  spec.add_dependency 'trailblazer', '~> 2.1'

  spec.add_development_dependency 'bundler'
  spec.add_development_dependency 'combustion', '~> 1.3'
  spec.add_development_dependency 'factory_bot'
  spec.add_development_dependency 'ffaker'
  spec.add_development_dependency 'pg'
  spec.add_development_dependency 'pry-byebug'
  spec.add_development_dependency 'rake', '~> 13.0'
  spec.add_development_dependency 'rspec', '~> 3.0'
  spec.add_development_dependency 'rspec-rails'
  spec.add_development_dependency 'strong_migrations'
end

When I run bundle install I had an error:

[!] There was an error parsing `Gemfile`: 
[!] There was an error parsing `Gemfile`: You cannot specify the same gem twice coming from different sources.
You specified that admin_panel (>= 0) should come from source at `engines/admin_panel` and source at `.`
. Bundler cannot continue.

 #  from engines/admin_panel/Gemfile:6
 #  -------------------------------------------
 #  
 >  gemspec
 #  
 #  -------------------------------------------
. Bundler cannot continue.

 #  from /app/Gemfile:171
 #  -------------------------------------------
 #  # Admin panel
 >  component 'admin_panel'
 #  -------------------------------------------

If I remove the gemspec call from the engine Gemfile, everything works fine, but it looks strange because I want to reuse this engine in other applications. The same will happen if I use your generators and create some kind of test engine or gem. It generates engine with Gemfile with gemspec call inside. And "bundle install" will throw an error again.

palkan commented 3 years ago

You specified that admin_panel (>= 0) should come from source at engines/admin_panel and source at .

Interesting; why .?

Which Bundler version do you use?

maiksimov commented 3 years ago

Gemfile.lock:

BUNDLED WITH
   1.17.3
palkan commented 3 years ago

That's probably the reason.

I couldn't find a relevant issue nor personal notes, but I had a feeling that we specifically upgraded to Bundler 2 to make eval_gemfile work as we expected.

maiksimov commented 3 years ago

I updated the version of the bundler to 2. But nothing changed, the errors remained the same

palkan commented 3 years ago

Unfortunately, I don't have an access to any engined codebase right now; would you be able to provide a minimal reproduction, so I can debug it?

luccasmaso commented 3 years ago

Hi. I'm having this same problem. But I actually did not understand where to place class Bundler::Dsl component function to be accessible inside the root Gemfile. I've tried placing inside /lib/scripts/component.rb, /lib/scripts/bunder/component.rb... but only because @maiksimov pointed out.

Thanks in advance

palkan commented 3 years ago

where to place class Bundler::Dsl component function to be accessible inside the root Gemfile

You can put the patch anywhere you want, just don't forget to require it at the top of the Gemfile.

We put the patch right in the Gemfile:

# frozen_string_literal: true

unless Bundler::Dsl.instance_methods.include?(:eval_gemfile_original)
  class Bundler::Dsl
    alias eval_gemfile_original eval_gemfile

    def eval_gemfile(gemfile, contents = nil)
      expanded_gemfile_path = Pathname.new(gemfile).expand_path(@gemfile&.parent)
      return if @gemfiles.any? { |path| path == expanded_gemfile_path }

      eval_gemfile_original(gemfile, contents)
    end

    alias eval_gemfile_without_duplication eval_gemfile

    def component(name, namespace: "engines")
      Dir.chdir(__dir__) do
        group name.to_sym do
          group :default do
            # Add engine as a dependency
            gem name, path: "#{namespace}/#{name}"

            # Add runtime non-RubyGems specs
            if File.readable?("#{namespace}/#{name}/Gemfile.runtime")
              eval_gemfile_without_duplication "#{namespace}/#{name}/Gemfile.runtime"
            end
          end

          # Add development deps to development and test groups
          expanded_spec_path = Pathname.new("#{namespace}/#{name}/#{name}.gemspec").expand_path(@gemfile&.parent)

          spec = Gem::Specification.load(expanded_spec_path.to_s)

          spec.dependencies.select { |s| s.type == :development }.each do |dep|
            next if @dependencies.find { |current_dep| current_dep.name == dep.name }

            gem dep.name, dep.requirement, group: [:development, :test]
          end
        end
      end
    end
  end
end

source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby File.read(File.join(__dir__, ".ruby-version")).strip

rails_version = File.read(File.join(__dir__, ".rails-version"))

gem "rails", rails_version

# == Core ==
gem "pg"
gem "puma"
gem "redis"
gem "webpacker"

# ...

group :development, :test do
  # ...
end

group :development do
  # ...
end

group :test do
  # ...
end

# == Engines ==

# NOTE: components must be loaded after everything to ensure we do not
# load already defined deps twice (we do check for that in the #component method).
#
# Loading the same dependency twice makes Bundler crazy in some cases.

# Core engine
component "core"

# Authentication engine
component "auth"

# Admin dashboard engine
component "admin"

@maiksimov btw, check the commend above; do you have component calls in the end of the Gemfile? Looks like there were some "crazy" issues I don't remember 🙂

palkan commented 3 years ago

@maiksimov Have you added the #eval_gemfile patch?

maiksimov commented 3 years ago

@palkan Hello! I created a public rails test project, added your scripts and created a test gem inside as it was written in the manual. I have errors like above (when run bundle install). Can you check it out? Maybe I did something wrong.

https://github.com/maiksimov/test-rails-project

palkan commented 3 years ago

Thanks for the demo project!

I found the problem: evaling Gemfile within gemspec inside doesn't really work (we actually didn't have this line in our setup, don't know where it came from).

I've updated the guides and docs to fix this: https://github.com/palkan/engems/commit/27a77c53166c48c745000eaae45213b1a6d3a2cd

tl;dr We need Gemfile.dev as well 🙂

Here is the fix for the demo project: https://github.com/palkan/test-rails-project/commit/a3079f4b9b3932946a742544122aafd23c2bad4b

maiksimov commented 3 years ago

@palkan Thanks!