Skip to content

Conversation

deivid-rodriguez
Copy link
Contributor

@deivid-rodriguez deivid-rodriguez commented Oct 19, 2021

What was the end-user or developer problem that led to this PR?

The pathname library has been gemified, so users should be able to specify it inside their Gemfile's. However, bundler uses the pathname library internally for a lot of things. This causes the pathname gem to be activated too early, possibly conflicting with user's choice of the gem.

What is your fix for the problem, implemented in this PR?

My fix is:

  • Remove pathname usages when possible.
  • Vendor the pathname gem, together with a pure ruby implementation of its C extension.

The one concern I have here is that now some widely used methods, such as Bundler.root will return a Bundler::Pathname, not a Pathname. If people are using normal typing on the result, rather than duck typing, things will break since the result is an object of a different class now. If we can rely on people not doing this, I can fully implement Pathname's interface to avoid any other breakage (right now I have only implemented the subset of functionality that bundler uses internally).

Fixes #5016.
Fixes ruby/pathname#12.

Make sure the following tasks are checked

command_execution.exitstatus = if status.exited?
status.exitstatus
elsif status.signaled?
128 + status.termsig
Copy link
Contributor

@doodzik doodzik Oct 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

\nit 128 is a magic number. I think it would add to maintainability if the number would live in a constant.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, that can be improved, we should probably add a link to https://tldp.org/LDP/abs/html/exitcodes.html and move it to a constant/variable with some non magical name. But it's unrelated to this PR, here I'm just moving this code around, the magic number is already present in the current master branch.

@doodzik
Copy link
Contributor

doodzik commented Oct 22, 2021

The one concern I have here is that now some widely used methods, such as Bundler.root will return a Bundler::Pathname, not a Pathname. If people are using normal typing on the result, rather than duck typing, things will break since the result is an object of a different class now.

I think it could be confusing for bundler's users if Bundler::Pathname isn't behaving like ruby's Pathname. I wonder if it would make more sense to return a string instead of exposing the internal implementation of Bundler::Pathname, which isn't subject to as much change. A user can still pass the string to their choice of Pathname implementation.

@deivid-rodriguez
Copy link
Contributor Author

deivid-rodriguez commented Oct 25, 2021

Bundler::Pathname should be behaving just like Pathname. Right now the interface is not yet complete because I only started by implementing the minimal subset of functionality used internally, but if we want to improve backwards compatibility of this PR, the whole thing should be reimplemented.

This way users should be able to use the whole set of Pathname methods on the result of Bundler.root. The problem is if users are using things like

root = Bundler.root

# stuff

if root.is_a?(Pathname)
  # stuff
end

or things like that, since that will change after this PR.

I think that should be weird enough to ignore it?

Another possibility could be to define some set of public bundler methods, and keep returning Pathname's for them, while defining equivalent ones that use Bundler::Pathname (like, Bundler.internal_root or whatever), and make bundler/setup call the internal ones.

But I tend to think the current approach is fine.

@doodzik
Copy link
Contributor

doodzik commented Oct 26, 2021

I think that should be weird enough to ignore it?

Yeah, the current approach seems fine to me. The potential problem seems very niche and pretty easy to resolve for the developer. Not sure why someone would need to check if Bundler.root is a Pathname anyways.

@andrewfader
Copy link

Got a TypeError: unexpected @path /home/andrew/.rvm/gems/ruby-3.1.0-preview1/gems/bootsnap-1.9.1/lib/bootsnap/load_path_cache/path.rb:103:in `to_s' on rails new with ruby 3.1-rc2 and rails 7-alpha

@doodzik
Copy link
Contributor

doodzik commented Nov 16, 2021

@andrewfader Interesting error. There is an issue in bootsnap that suggests that the error is caused by multiple gem activation of pathname. Would you be able to test if this PR fixes your issue?

@andrewfader
Copy link

@doodzik I believe so, I ran setup.rb from this branch and then I was able to do it without error.

@andrewfader
Copy link

Actually it may still be broken. Here's the new error /home/andrew/.rvm/rubies/ruby-3.1.0-preview1/lib/ruby/3.1.0/pathname.rb:49:in `basename': no implicit conversion of nil into String (TypeError) from /home/andrew/.rvm/rubies/ruby-3.1.0-preview1/lib/ruby/3.1.0/pathname.rb:49:in `chop_basename' from /home/andrew/.rvm/rubies/ruby-3.1.0-preview1/lib/ruby/3.1.0/pathname.rb:374:in `plus' from /home/andrew/.rvm/rubies/ruby-3.1.0-preview1/lib/ruby/3.1.0/pathname.rb:354:in `+' from /home/andrew/.rvm/rubies/ruby-3.1.0-preview1/lib/ruby/3.1.0/pathname.rb:417:in `block in join' from /home/andrew/.rvm/rubies/ruby-3.1.0-preview1/lib/ruby/3.1.0/pathname.rb:415:in `reverse_each' from /home/andrew/.rvm/rubies/ruby-3.1.0-preview1/lib/ruby/3.1.0/pathname.rb:415:in `join'

@deivid-rodriguez
Copy link
Contributor Author

Is that all? It would seem to me that backtrace is missing some entries.

@andrewfader
Copy link

andrewfader commented Nov 16, 2021

Here you go. Sorry about that:


❯ DISABLE_BOOTSNAP=1 rails s > foo.txt
/home/andrew/.rvm/rubies/ruby-3.1.0-preview1/lib/ruby/3.1.0/pathname.rb:20: warning: already initialized constant Pathname::TO_PATH
/home/andrew/.rvm/gems/ruby-3.1.0-preview1/gems/pathname-0.2.0/lib/pathname.rb:20: warning: previous definition of TO_PATH was here
/home/andrew/.rvm/rubies/ruby-3.1.0-preview1/lib/ruby/3.1.0/pathname.rb:22: warning: already initialized constant Pathname::SAME_PATHS
/home/andrew/.rvm/gems/ruby-3.1.0-preview1/gems/pathname-0.2.0/lib/pathname.rb:22: warning: previous definition of SAME_PATHS was here
/home/andrew/.rvm/rubies/ruby-3.1.0-preview1/lib/ruby/3.1.0/pathname.rb:34: warning: already initialized constant Pathname::SEPARATOR_LIST
/home/andrew/.rvm/gems/ruby-3.1.0-preview1/gems/pathname-0.2.0/lib/pathname.rb:34: warning: previous definition of SEPARATOR_LIST was here
/home/andrew/.rvm/rubies/ruby-3.1.0-preview1/lib/ruby/3.1.0/pathname.rb:35: warning: already initialized constant Pathname::SEPARATOR_PAT
/home/andrew/.rvm/gems/ruby-3.1.0-preview1/gems/pathname-0.2.0/lib/pathname.rb:35: warning: previous definition of SEPARATOR_PAT was here
/home/andrew/.rvm/rubies/ruby-3.1.0-preview1/lib/ruby/3.1.0/pathname.rb:41: warning: already initialized constant Pathname::ABSOLUTE_PATH
/home/andrew/.rvm/gems/ruby-3.1.0-preview1/gems/pathname-0.2.0/lib/pathname.rb:41: warning: previous definition of ABSOLUTE_PATH was here
/home/andrew/.rvm/rubies/ruby-3.1.0-preview1/lib/ruby/3.1.0/pathname.rb:49:in `basename': no implicit conversion of nil into String (TypeError)
	from /home/andrew/.rvm/rubies/ruby-3.1.0-preview1/lib/ruby/3.1.0/pathname.rb:49:in `chop_basename'
	from /home/andrew/.rvm/rubies/ruby-3.1.0-preview1/lib/ruby/3.1.0/pathname.rb:374:in `plus'
	from /home/andrew/.rvm/rubies/ruby-3.1.0-preview1/lib/ruby/3.1.0/pathname.rb:354:in `+'
	from /home/andrew/.rvm/rubies/ruby-3.1.0-preview1/lib/ruby/3.1.0/pathname.rb:417:in `block in join'
	from /home/andrew/.rvm/rubies/ruby-3.1.0-preview1/lib/ruby/3.1.0/pathname.rb:415:in `reverse_each'
	from /home/andrew/.rvm/rubies/ruby-3.1.0-preview1/lib/ruby/3.1.0/pathname.rb:415:in `join'
	from /home/andrew/.rvm/gems/ruby-3.1.0-preview1/gems/railties-7.0.0.alpha2/lib/rails/application/configuration.rb:445:in `credentials_available_for_current_env?'
	from /home/andrew/.rvm/gems/ruby-3.1.0-preview1/gems/railties-7.0.0.alpha2/lib/rails/application/configuration.rb:429:in `default_credentials_content_path'
	from /home/andrew/.rvm/gems/ruby-3.1.0-preview1/gems/railties-7.0.0.alpha2/lib/rails/application/configuration.rb:71:in `initialize'
	from /home/andrew/.rvm/gems/ruby-3.1.0-preview1/gems/railties-7.0.0.alpha2/lib/rails/application.rb:381:in `new'
	from /home/andrew/.rvm/gems/ruby-3.1.0-preview1/gems/railties-7.0.0.alpha2/lib/rails/application.rb:381:in `config'
	from /home/andrew/.rvm/gems/ruby-3.1.0-preview1/gems/railties-7.0.0.alpha2/lib/rails/railtie.rb:146:in `config'
	from /home/andrew/workspace/trident/config/application.rb:12:in `<class:Application>'
	from /home/andrew/workspace/trident/config/application.rb:11:in `<module:Trident>'
	from /home/andrew/workspace/trident/config/application.rb:9:in `<top (required)>'
	from /home/andrew/.rvm/gems/ruby-3.1.0-preview1/gems/zeitwerk-2.5.1/lib/zeitwerk/kernel.rb:35:in `require'
	from /home/andrew/.rvm/gems/ruby-3.1.0-preview1/gems/zeitwerk-2.5.1/lib/zeitwerk/kernel.rb:35:in `require'
	from /home/andrew/.rvm/gems/ruby-3.1.0-preview1/gems/railties-7.0.0.alpha2/lib/rails/commands/server/server_command.rb:137:in `block in perform'
	from <internal:kernel>:90:in `tap'
	from /home/andrew/.rvm/gems/ruby-3.1.0-preview1/gems/railties-7.0.0.alpha2/lib/rails/commands/server/server_command.rb:134:in `perform'
	from /home/andrew/.rvm/gems/ruby-3.1.0-preview1/gems/thor-1.1.0/lib/thor/command.rb:27:in `run'
	from /home/andrew/.rvm/gems/ruby-3.1.0-preview1/gems/thor-1.1.0/lib/thor/invocation.rb:127:in `invoke_command'
	from /home/andrew/.rvm/gems/ruby-3.1.0-preview1/gems/thor-1.1.0/lib/thor.rb:392:in `dispatch'
	from /home/andrew/.rvm/gems/ruby-3.1.0-preview1/gems/railties-7.0.0.alpha2/lib/rails/command/base.rb:87:in `perform'
	from /home/andrew/.rvm/gems/ruby-3.1.0-preview1/gems/railties-7.0.0.alpha2/lib/rails/command.rb:48:in `invoke'
	from /home/andrew/.rvm/gems/ruby-3.1.0-preview1/gems/railties-7.0.0.alpha2/lib/rails/commands.rb:18:in `<top (required)>'
	from bin/rails:4:in `require'
	from bin/rails:4:in `<main>'

@deivid-rodriguez
Copy link
Contributor Author

What are the contents of bin/rails?

@andrewfader
Copy link

@deivid-rodriguez I deleted my whole project and did rails new again and now rails s works fine. So I guess my issue is now solved. But here was bin/rails if it's at all helpful.

#!/usr/bin/env ruby
APP_PATH = File.expand_path("../config/application", __dir__)
require_relative "../config/boot"
require "rails/commands"

@deivid-rodriguez
Copy link
Contributor Author

deivid-rodriguez commented Nov 17, 2021

Mmm, alright. I was blind guessing that maybe your rails binstub was loading Pathname before bundler/setup is run. But no. Bundler generated binstubs were actually doing that before this PR so users using bundler generated binstubs will also need to regenerate them (not only to upgrade bundler) in order to get rid of that.

@andrewfader
Copy link

I'm guessing I didn't completely eradicate something - or something to do with rvm or bootsnap/spring running in the background

Not that I need it, but reads better.
When launching bundler subprocesses for end to end testing, all of them
will load the `spec/support/rubygems_version_manager.rb` file passed as
a ruby's `-r` flag.

Unfortunately this file depends on `pathname`, so unless we drop that
dependency, we can't really test support for including the `pathname`
gem in the `Gemfile`.

This commit implements some refactorings to avoid loading `pathname`
inside `bundler` test subprocesses.
`Pathname#sub` returns a new `Pathname` already.
We can use strings earlier on.
For backwards compatibility.
@deivid-rodriguez
Copy link
Contributor Author

Alright I implemented the whole Pathname API in ruby, except for Pathname#taint, Pathname#untaint, which are deprecated and seem fine to ignore, and Pathname#<=>, which explicitly makes sure Pathname objects can only be compared with Pathname objects. If the result of Bundler.root (now a Bundler::Patname) is sorted with other Pathname's, I don't think the sorting will work anyways, since Bundler.root won't be accepted as a valid element to compare to anyways.

I guess next step is to vendor the pathname test suite and make sure it passes against our pure ruby implementation.

@doodzik
Copy link
Contributor

doodzik commented Nov 22, 2021

Looking at the linked PR in the pathname repo, pathname doesn't support rubies older than 2.7. We probably need to vendor an earlier version of the library to be used with rubygems, correct? Or would it make more sense to wait with vendoring pathname before we deprecated an older ruby version #3260.

@deivid-rodriguez
Copy link
Contributor Author

That's a good point! Given that CI is fully green, my guess is that issues on old rubies are C-related (they were surfaced at gem compilation time) and that our pure ruby implementation doesn't suffer from them. But I think we'll find out for sure once I vendor pathname's test suite too, and see how our implementation behaves against it.

@andrewfader
Copy link

Maybe I could help or pair on this. Lmk?

@deivid-rodriguez
Copy link
Contributor Author

This is only missing, I believe, to make sure that it's fully compatible with the real pathname library. For that, my idea was to import the pathname test suite, with the proper modifications to run against our ruby version, and run it as part of our CI. I guess I'll be able to come back to this in a couple of months, but in the mean time feel free to move that forward, I'm happy to help!

@andrewfader
Copy link

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

URI constants & core dump. Problem with Bundler, Ruby, RubyGems, or ruby-install? A bunch of warnings showing up after migrating to version 0.2.0

4 participants