Project

General

Profile

Actions

Feature #18376

open

Version comparison API

Added by vo.x (Vit Ondruch) 10 months ago. Updated 9 months ago.

Status:
Open
Priority:
Normal
Assignee:
-
Target version:
-
[ruby-core:106360]

Description

Is there a chance to have version comparison API? For example if Gem::Version was extracted into ::Version. This idea was triggered by this PR 1 and 2, where the Gem::Version API is used for comparing Ruby versions. While RubyGems might be available everywhere, it does not look correct to introduce dependencies on RubyGems into libraries which could run without them just fine.


Related issues 2 (0 open2 closed)

Related to Ruby master - Feature #17684: Remove `--disable-gems` from release version of RubyClosedActions
Is duplicate of Ruby master - Feature #5861: String#version_compareRejectedmatz (Yukihiro Matsumoto)01/08/2012Actions

Updated by Eregon (Benoit Daloze) 10 months ago

I think this could be useful.
I've seen several cases like this, notably I remember https://github.com/ffi/ffi/blob/master/lib/ffi.rb.
There is a workaround there: (RUBY_ENGINE_VERSION.split('.').map(&:to_i) <=> [20, 1, 0]) >= 0 since RubyGems might not be loaded yet at that point.
That's not really pretty, so a builtin Version seems nicer.

One issue I could imagine is different version conventions and how to compare them.

Updated by vo.x (Vit Ondruch) 10 months ago

Eregon (Benoit Daloze) wrote in #note-1:

One issue I could imagine is different version conventions and how to compare them.

Opportunity for standardization? :) RubyGems with their version checking are doing that anyway.

Updated by deivid (David Rodríguez) 10 months ago

My opinion is that the right place for this functionality is RubyGems and that adding/duplicating it as builtin functionality because someone might need it before rubygems is loaded, or while using --disable-gems is overkill.

Updated by Eregon (Benoit Daloze) 10 months ago

Note that for default gems (which ffi is on jruby/truffleruby) it's also not possible to use RubyGems, so I think there is a non-trivial amount of cases.

Additionally, if people use bundler standalone mode, there is also no RubyGems loaded (or it's suboptimal to load it).

Updated by deivid (David Rodríguez) 10 months ago

Yeah, I still think it's completely overkill, and people will be confused because we will have two different things that do the same thing.

Updated by vo.x (Vit Ondruch) 10 months ago

RubyGems could adopt this API and slowly phase out its own version.

Updated by pocke (Masataka Kuwabara) 10 months ago

::Version will be conflict with optparse's convention. Probably we need to consider the naming.
ref: https://github.com/ruby/ruby/blob/fe506d7945788f4c3243e9ec25c20c5dbd315073/lib/optparse.rb#L1208-L1213

Actions #8

Updated by naruse (Yui NARUSE) 10 months ago

  • Related to Feature #17684: Remove `--disable-gems` from release version of Ruby added
Actions #9

Updated by naruse (Yui NARUSE) 10 months ago

Updated by naruse (Yui NARUSE) 10 months ago

If you are saying we want to use some rubygems feature with --disable-gems, we need to raise #17684 again.

Updated by deivid (David Rodríguez) 10 months ago

RubyGems could adopt this API and slowly phase out its own version.

Thanks for bringing that up. The more I think about it, the more I dislike it. I don't think version comparison is common enough to be something builtin to core, let alone if it's only to satisfy the needs of a handful of default gems (only ffi has been mentioned so far). It feels to me that in order to save one line of code in ffi, I have to go through the trouble of slowly phasing out what's probably the most commonly used rubygems constant in the wild. Definitely not a fan :sweat_smile:.

Updated by Eregon (Benoit Daloze) 10 months ago

String#version_compare (#5861) would likely be a much less invasive way to add this, and so probably better.

Updated by deivid (David Rodríguez) 10 months ago

That feature request was rejected by Matz because of not being common enough, and nothing seems to have changed in that regard.

Updated by austin (Austin Ziegler) 10 months ago

deivid (David Rodríguez) wrote in #note-11:

RubyGems could adopt this API and slowly phase out its own version.

Thanks for bringing that up. The more I think about it, the more I dislike it. I don't think version comparison is common enough to be something builtin to core, let alone if it's only to satisfy the needs of a handful of default gems (only ffi has been mentioned so far). It feels to me that in order to save one line of code in ffi, I have to go through the trouble of slowly phasing out what's probably the most commonly used rubygems constant in the wild. Definitely not a fan :sweat_smile:.

I have at least two libraries that I maintain using the Gem::Version API for feature enablement or back-port support. Some libraries that I have used for projects set VERSION = Gem::Version.new('1.2.3'); at least some of the gems that I have used in the past also use the version API in the same way that I do.

People reach for Gem::Version because it’s the most feature-complete readily available semver library for Ruby. As a comparison, Elixir does provide a Version module as part of core.

If we don’t want to add a ::Version constant to core, then perhaps the best thing to do would be for Gem::Version to be made available separately from the rest of RubyGems, possibly importing it into core (this has the benefit of not requiring an API change for anything that uses it). Or maybe (and this is a bit of a bootstrap problem), Gem::Version could be packaged as its own gem for people to import separately if they need gem versioning.

This isn’t a frequent problem, but I disagree that it’s "not common enough". I have no clue what would happen with the gems that I maintain (mime-types is perhaps the easiest version to test) if run with --disable-gems. I think that this is worth adding to core.

Updated by deivid (David Rodríguez) 10 months ago

I'm definitely aware that this is used for what you point out. But most gem authors just use it and don't worry about "what if rubygems is not available?", which I think it's reasonable because rubygems is part of ruby and they are developing a gem after all.

Updated by vo.x (Vit Ondruch) 10 months ago

There is probably more to this issue.

  1. On the first place, I'd love if everybody used introspection instead of version checks. We would not need to have this conversation. But honestly, I don't really know how to detect the kwargs.
  2. Ruby used to have simple version comparison up until 2.1.10. The string comparison worked just fine. Nevertheless "-preview1" would break it anyway.
  3. I don't know what is suggested way to compare Ruby versions since Ruby 2.1.10. String#version_compare would be certainly quite easy interface. Or RUBY_VERSION could provide some operator for version comparisons. Gem::Version.new("2.6.18")<=>Gem::Version.new("2.6.3") was pointed out already in #5861 but that means the third party dependency.
  4. If the third party dependency is deemed OK, then it should be accompanied by require "rubygems/version" to ensure it is available. But that on itself won't work and require "rubygems" is needed. I don't think that it can be assumed that dependencies are loaded.

Updated by vo.x (Vit Ondruch) 10 months ago

deivid (David Rodríguez) wrote in #note-15:

But most gem authors

This is OT, but just FTR, I might be considered "gem author", but I am Ruby developer on the first place. RubyGems are mostly convenient way of code distribution, but there are other ways (e.g. git clone or RPM/DNF on my platform).

Updated by austin (Austin Ziegler) 10 months ago

deivid (David Rodríguez) wrote in #note-15:

I'm definitely aware that this is used for what you point out. But most gem authors just use it and don't worry about "what if rubygems is not available?", which I think it's reasonable because rubygems is part of ruby and they are developing a gem after all.

Yet there's people who use --disable-gems for various reasons, yet might also use a standalone bundle. I just tried this and I do get an uninitialized constant error with --disable-gems. I know that gem authors treat Rubygems as a given, but it feels…iffy to me that we depend on this this way. I did just see that Gem::Version does not get defined in an idempotent way, as it is declared with class Gem::Version and not class Gem; class Version: ruby --disable-gems -rrubygems/version -e 'p Gem::Version' results in uninitialized constant Gem.

At any rate, I am using Elixir much more often these days, where I do have a Version module built-in for those (admittedly rare) cases where I need version comparison—and it is really nice to have something readily available as part of the core.

Updated by deivid (David Rodríguez) 10 months ago

Of course, but I believe most people who need this are gem authors, and most gem authors use rubygems. People developing apps usually support a single version of gems or ruby, so generally don't need this kind of feature? I think most Ruby developers assume they can use Gem::Version freely, because Rubygems is normally available by default.

Anyways, I think my opinion is pretty clear so I'll refrain from commenting further.

Updated by deivid (David Rodríguez) 10 months ago

Sorry @austin (Austin Ziegler), I was actually replying to @vo.x (Vit Ondruch) in my previous comment, but our comments crossed. Anyways, I don't have much to add, really!

Updated by unasuke (Yusuke Nakamura) 10 months ago

I think it is useful for not just gem authors but also application developers.
In my known case of providing an SDK and API as service, version string compare is often used in parsing SDK version string on request from SDK to API server.
Gem::Version is commonly used for comparing "semantic version"-ed version string in that situation. Because it can use without any other gem installed. But that is not "Gem". It brings tiny context confusion.

If I can use the same function with ::Version, it's useful. Not that I'm against it, but I am not sure if it's worth introducing this as a standard library because it might require a lot of work to resolve all of the above concerns.

Updated by deivid (David Rodríguez) 10 months ago

Thinking a bit more about this, current Gem::Version functionality seems quite independent, so there may be a way to provide this feature that's also low friction for rubygems maintainers.

Something like https://github.com/rubygems/rubygems/pull/5136 would already address the "I don't want to load all of rubygems just to compare some versions" concern. But if we extracted rubygems version.rb file to a new default gem, that would provide the same functionality through the ::Version class for users that explicitly require "version", also addressing the "Gem::Version is confusing for some version usages not related to gems" concern. Nothing would have to change for us in rubygems, we would need to vendor version.rb under the Gem namespace like we do for other default gems, which is exactly what we are already doing.

Updated by Eregon (Benoit Daloze) 10 months ago

deivid (David Rodríguez) wrote in #note-22:

Something like https://github.com/rubygems/rubygems/pull/5136 would already address the "I don't want to load all of rubygems just to compare some versions" concern.

Unfortunately this does not work if RubyGems is loaded lazily via autoload :Gem, 'rubygems' (it will load all of RubyGems on require 'rubygems/version').

And also this seems quite confusing if the Gem constant is defined but all the constants/classes under Gem are not there and would be NameError when accessing them, unless require 'rubygems' is done before those accesses:

$ ruby --disable-gems -e 'require "rubygems/version"; p Gem::Version; p Gem::Specification'
Gem::Version
-e:1:in `<main>': uninitialized constant Gem::Specification (NameError)

For this reason, IMHO that PR should be reverted.

But if we extracted rubygems version.rb file to a new default gem, that would provide the same functionality through the ::Version class for users that explicitly require "version", also addressing the "Gem::Version is confusing for some version usages not related to gems" concern. Nothing would have to change for us in rubygems, we would need to vendor version.rb under the Gem namespace like we do for other default gems, which is exactly what we are already doing.

That seems a great idea to me.

Updated by deivid (David Rodríguez) 10 months ago

And also this seems quite confusing if the Gem constant is defined but all the constants/classes under Gem are not there and would be NameError when accessing them, unless require 'rubygems' is done before those accesses:

I mean, of course, right? What else would you expect? Isn't that the whole point?

Updated by deivid (David Rodríguez) 10 months ago

To clarify, won't users of --disable-gems will expect that nothing under Gem is defined except for the explicitly required rubygems/version? Isn't that what they want?

Updated by Eregon (Benoit Daloze) 10 months ago

deivid (David Rodríguez) wrote in #note-24:

I mean, of course, right? What else would you expect? Isn't that the whole point?

Not really, I think the whole point is to have a version API without needing to load RubyGems and working even if RubyGems is not loaded (--disable-gems, Bundler standalone, etc).
The side-effect of breaking any access under Gem in some cases is IMHO bad enough that that PR is not worth it.
It'll break any defined?(Gem) usage, etc.
We've had empty YAML, Date and other modules defined in the past, it's really confusing and IMHO should be avoided at all costs.

The version default gem idea OTOH has none of these problems, so I think that's the way to go for this issue.

Updated by deivid (David Rodríguez) 10 months ago

I disagree that this will "break" anything, but I'll revert for now until we gather some more opinions on my better idea.

Updated by deivid (David Rodríguez) 10 months ago

We reverted it for now.

Updated by janosch-x (Janosch Müller) 9 months ago

pocke (Masataka Kuwabara) wrote in #note-7:

::Version will be conflict with optparse's convention. Probably we need to consider the naming.

Maybe Semver?

This is a bit clunky but would arguably indicate the core functionality better than Version. (Or SemVer, but lower case "v" would match Errno, Regexp.)

Actions

Also available in: Atom PDF