Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inherit config from gems #290

Closed
lenntt opened this issue Jun 20, 2013 · 11 comments
Closed

Inherit config from gems #290

lenntt opened this issue Jun 20, 2013 · 11 comments

Comments

@lenntt
Copy link

lenntt commented Jun 20, 2013

[Feature request]

Hi,

Rake tasks are pretty much the way to go in ruby. its great for having things run in a build.

Also, rake tasks can be distrubuted by gems/rails engines etc, to reduce code duplication per project.
E.g. it is now hard to share the config like that. I was thinking of something like:

Rubocop::Config::inherit_from(File.join(MyEngine::Engine.root, 'config/rubocop_checks.yml'))

(since you cant do this in .yml)

It would be great if there's an API to support this.

My current rake task that is used by my build task:

desc 'Verify code conventions'
task :rubocop do
  puts 'Rubocop checking application ...'
  system 'bundle exec rubocop --silent'
  fail if $?.exitstatus != 0
end
@lee-dohm
Copy link

I can't comment if a Rake task is the way the project team wants to go, but I can help with one thing. This is my Rake task for RuboCop:

desc 'Run style checks'
task :rubocop do
  sh 'rubocop'
end

The sh function provided by Rake automatically fails the task if the exit status is not zero. It also emits the command you give it so you won't necessarily need the puts line you have also.

@lenntt
Copy link
Author

lenntt commented Jun 21, 2013

That indeed is a lot easier.

@bbatsov
Copy link
Collaborator

bbatsov commented Jun 21, 2013

I'm not sure what benefits you'd get from a rake task for rubocop - personally I run it standalone always, since rubocop is project-aware anyways. As a matter a fact I run rspec this way as well.

@lenntt
Copy link
Author

lenntt commented Jun 21, 2013

rubocop is project-aware, but that doesnt mean configurations should not be able to be shared.
the feature request is not to have a rake task available (see previous comments, its easy to make), but to support sharing its configuration among other projects. (I'll update the title)

Use case: I've disabled some cops (because I do not agree with the style-guide on all points) and do not want to copy paste (and maintain) between this .yml in all projects. Therefore would like to put this configuration in one file, put it in a gem and load it in all my projects.

The place for me to load the configuration from a gem would be in a rake task, but I just realize, that is just in my setting, so it can be whatever other ruby file that works.

@jonas054
Copy link
Collaborator

Today we have the inherit_from directive that you can put in your .rubocop.yml file, but it doesn't have the ability to do advanced thigs like figuring out which is the newest version of a gem, because it's just YAML code.

One soluton could be to add ERB pre-processing of the config files so you could do things like

# .rubocop.yml

<% require 'rubocop_config' %>
inherit_from <%= path_to_rubocop_config + '/config/rubocop_checks.yml' %>

but it means we have to mix languages in the config files (someting Robert C. Martin warns about in Clean Code), and it seems pretty complicated.

How about you do this in the .rubocop.yml files that should share configuration:

inherit_from common_config/rubocop_checks.yml

and then keep the common_config symbolic link updated in the rake task that runs rubocop?

@yujinakayama
Copy link
Collaborator

Currently I think it's rather a role of external tools than RuboCop itself.

If you really want to use just one configuration file in all your projects, put the .rubocop.yml in your home directory and remove all .rubocop.yml in your projects.

If you prefer more flexible way, I think you can use git submodule. Though this is similar to @jonas054 's proposal.

$ cd some-project
$ git submodule add https://github.com/foo/common-config.git
$ vi .rubocop.yml
inherit_from: common-config/default.yml

# project specific configuration
# ...

When you've updated the common-config repository, you can keep the project's configuration up-to-date by running this command:

$ git submodule foreach git pull

@lenntt
Copy link
Author

lenntt commented Jun 24, 2013

@jonas054 and @yujinakayama
I both agree and disagree with the both of you.
Maybe it isn't the responsibility for a tool to do this BUT, I think one of the beautiful things in the Ruby world, because we all suffer from problems like this, sharing configs, sharing libraries etc. most of the APIs we use are not needing file-based solutions.

@lee-dohm
Copy link

Let's identify the scenarios we want to enable. We currently have:

  1. Configurationless execution, i.e. no .rubocop.yml file at all
  2. Per-project configuration, i.e. single .rubocop.yml in the root directory of the project
  3. Per-subtree configuration, i.e. .rubocop.yml in the root directory with one or more .rubocop.yml files in subdirectories of the project root

Now ... the typical way to enable configuration sharing would be to have a per-user and per-machine configuration. Anything else is typically up to the user. We do enable either of these scenarios with the inherit_from method, but we could make it more explicit.

I just had an idea ...

You could create a gem with your configuration file and use bundle package to install it to the ./vendor/cache directory in your project. Then you could just use inherit_from to include it into the per-project configuration.

But after going through the options of how to implement this and thinking of ways that other Ruby utilities work, I think I'm with @yujinakayama, that this level of configuration sharing is orthogonal to RuboCop.

@lenntt
Copy link
Author

lenntt commented Jun 24, 2013

Thanks for all the help.

I'll close this for now, non-file based configuration doesn't seem to be on rubocops roadmap.

I think I'll eventually write a ruby wrapper_method that is put in a gem that generates a .rubocop.yml. (combining a general project_rubocop.yml and the common_rubocop.yml loaded from the gem) or something.

@lenntt lenntt closed this as completed Jun 24, 2013
@lenntt
Copy link
Author

lenntt commented Jul 3, 2013

I made a construction like this, and it works ok for me:

desc 'Run code style checks'
task :rubocop do
  MyGem::RubocopUtils.copy_default_config(File.expand_path('..', __FILE__))
  sh 'rubocop lib/ spec/ --format progress'
end

@jhansche
Copy link

I've also recently run into a situation where it would be helpful to inherit a rubocop config from a gem's library path. I'm not saying it's up to Rubocop to install the gem, or determine the most up to date version, or anything like that; but if the project is using a gem, then presumably that gem has already been installed, and presumably executing rubocop would be done from a context in which the gem is already accessible.

To simplify the implementation, it could be a new yml key (something like inherit_from_gem, which takes a Hash of <gem_name>: <path_within_gem/rubocop.yml>):

inherit_from: .rubocop_todo.yml

inherit_from_gem:
  cucumber: 'config/rubocop.yml'
  my_shared_gem: 'path/to/my/rubocop.yml'

AllCops:
  # ...

And the config loader would essentially perform something like:

def resolve_inheritance_from_gem(gem_name, relative_config_path)
  spec = Gem::Specification.find_by_name(gem_name)
  f = File.join(spec.gem_dir, relative_config_path)
  load_file(f)
end

If the gem isn't available, it could either be silently ignored, or a proper error could be emitted suggesting that the required gem needs to be installed (or suggesting bundle install if a Gemfile exists, for example).

I understand the argument that this may be orthogonal to Rubocop, but it's not like this is anything that requires particularly obscure Ruby -- most projects successfully make use of shared gems, and if a project wants to use a shared rubocop configuration (without the need to maintain any symlinks, submodules, or ~/.rubocop.yml files), I don't see that as something that should be wildly avoided or discouraged. Using the DEFAULT_FILE could even piggy back on this, such that the default rubocop file would be loaded using:

resolve_inheritance_from_gem('rubocop', 'config/default.yml')

I don't mean to resurrect a 2-year-old issue that was closed, but really all the alternatives given do not lend themselves to a "works-out-of-the-box" project -- i.e., where a Jenkins CI job can easily invoke rubocop for the project with:

bundle install
bundle exec rubocop

In fact, I would consider all of the suggested alternatives above specifically discouraged, because they each require external tools (symlinks, which are not supported on all platforms; git submodule which is specific to the git SCM; a Rake task, which is specific to using rake at all, and many projects do not; or a shell script, which is also going to be platform-dependent in one way or another) to achieve the same end result, whereas allowing RuboCop to be gem-aware keeps all the behavior of loading a config from a gem path self-contained in the Ruby language -- the very language it is intended to work on.

jhansche pushed a commit to MeetMe/rubocop that referenced this issue Oct 7, 2015
The config directive should be in Hash format, with the `gem_name`
as the key, and the `relative_path_to_config` as the value. Ex:

    inherit_from: .rubocop_todo.yml

    inherit_gem:
        cucumber: config/rubocop.yml
        mygem: path/to/my/shared/rubocop.yml
bbatsov added a commit that referenced this issue Oct 8, 2015
Resolve #290 to inherit rubocop config from a gem
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants