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

logstash-core* version constrains problem #4236

Closed
colinsurprenant opened this issue Nov 19, 2015 · 47 comments
Closed

logstash-core* version constrains problem #4236

colinsurprenant opened this issue Nov 19, 2015 · 47 comments

Comments

@colinsurprenant
Copy link
Contributor

currently our gemspecs uses constrains on logstash-core (and soon logstash-core-event), let's call them the core gems, using a pattern similar to:

s.add_runtime_dependency "logstash-core", '>= 1.4.0', '< 2.0.0'

the problem with this notation is that a logstash-core gem release version 2.0.0.rc1 is considered prior to 2.0.0, because it is a "pre-release" version so a gem version 2.0.0.rc1 would satisfy the < 2.0.0 constrain.

Obviously the intent in specifying < 2.0.0 is to exclude anything 2.0.ish but I guess that semantically the current behaviour is arguable.

One possible solution is to specify ~> 1.4 but then this notation does not allow any pre-release versions to satisfy the constrain which is bad since we need to release pre-release beta/rc packages that uses pre-release gem versions.

I am not sure what our options are. A solution is required for current and future releases and should ideally be also applied to the 1.5 branch.

This related to #4235 logstash-plugins/logstash-output-file#22 #4188 #4231

@colinsurprenant
Copy link
Contributor Author

maybe use a crazy high version number for the upper bound?

s.add_runtime_dependency "logstash-core", '>= 1.4.0', '< 1.999.0' 

@ph ph added the discuss label Nov 19, 2015
@jordansissel
Copy link
Contributor

Ugh. SemVer strikes again :(

@jordansissel
Copy link
Contributor

I think I'm OK with 1.999.0 w/ a comment explaining why.

Alternately, I think the "right" solution is probably using ~> 1.4.0 which should enforce the same thing as the < 1.999.0 but with maybe more clear intent?

@jordansissel
Copy link
Contributor

I totally didn't finish reading your description. I see oyu already proposed ~> hehe

@jordansissel
Copy link
Contributor

We can try < 1.999.0 w/ a comment explaining the intent (prerelease/beta allowed) and see how that flies?

@purbon
Copy link
Contributor

purbon commented Nov 19, 2015

@colinsurprenant your proposal looks ugly to me (too hacky for now), I would do a bit more research or stick with the `~>`` as I proposed in logstash-plugins/logstash-output-file#22.

@jordansissel
Copy link
Contributor

I comfort myself in that this isn't a unicode problem, at least ;P

@colinsurprenant
Copy link
Contributor Author

@purbon but ~> is not a solution because we can't use pre-release gems which we do when we release betas or RCs.

I am not saying < 1.999.0 is the only solution, it's just the first one that came to mind.

@colinsurprenant
Copy link
Contributor Author

wondering if it would make a difference if we use < 2 or < 2.0 instead of < 2.0.0 ?

@colinsurprenant
Copy link
Contributor Author

nope.

irb(main):011:0> Gem::Requirement.new(">= 1.4.0", "< 2.0.0").satisfied_by?(Gem::Version.new("2.0.0.beta1"))
=> true
irb(main):012:0> Gem::Requirement.new(">= 1.4.0", "< 2.0").satisfied_by?(Gem::Version.new("2.0.0.beta1"))
=> true
irb(main):013:0> Gem::Requirement.new(">= 1.4.0", "< 2").satisfied_by?(Gem::Version.new("2.0.0.beta1"))
=> true

@colinsurprenant
Copy link
Contributor Author

note that the above is only the rubygem requirement validation and does not account for bundler's selection, which excludes pre releases when using ~> with a gem released version, because on the rubygem side this satisfies the constrain

irb(main):014:0> Gem::Requirement.new("~> 1.4").satisfied_by?(Gem::Version.new("1.5.0.beta1"))
=> true

but as expected not this

rb(main):015:0> Gem::Requirement.new("~> 1.4").satisfied_by?(Gem::Version.new("2.0.0.beta1"))
=> false

but bundler won't select it in the first place.

@colinsurprenant
Copy link
Contributor Author

another option would be to publish pre-release versions one minor version up, for example with the constrain < 2.0.0 if we release a version 2.1.0.beta1 instead of 2.0.0.beta1 then it will be ok.

irb(main):025:0> Gem::Requirement.new(">= 1.4", "< 2.0.0").satisfied_by?(Gem::Version.new("2.1.0.beta1"))
=> false

the problem with this approach is that it is error prone. if we forget not to release X.0.0.prewhatever version then we get burn. in that respect, using a high minor version constrain still seems the best & safest option.

irb(main):026:0> Gem::Requirement.new(">= 1.4", "< 1.999.0").satisfied_by?(Gem::Version.new("2.0.0.beta1"))
=> false

@jordansissel
Copy link
Contributor

I'm leaning towards < X.999.0 because it exhibits the correct behavior. The consequence is a weird-looking version constraint, and I think we can resolve that by having a comment explain what the version constraint is for.

Thoughts?

@purbon
Copy link
Contributor

purbon commented Nov 25, 2015

@jordansissel for me using < X.999.0 is reasonable as it fixed the issue with pre releases, but it looks very strange to me as is not really fixing the problem, is a workaround. I know going > 999 versions is going to be exceptional, but still feels awkward.

@colinsurprenant I agree, using ~> is not a solution either as it prevents us from using pre releases.

I would like us to step a bit, and thing on possible ways to have a clear description of it. Opening an issue with rubygems to clarify this now.

@purbon
Copy link
Contributor

purbon commented Nov 25, 2015

What about using something like ">= 1.4", "< 2.0.0.a", as pre releases are always sorted by alphabetical order this will select all gems (including pre releases) between 1.4 and everything that is lower than the first 2.0.0 pre release. In my opinion this is the clear option we've right now, at my understanding.

@colinsurprenant
Copy link
Contributor Author

personally I find it easier to comprehend the intention when using < X.999.0 and we know it will always work regardless if rubygems changes the constrains semantics for any reason. I think that defensively < X.999.0 is better than "< 2.0.0.a".

As for opening an issue with Rubygems, let's not forget that the constrain semantic of "< 2.0.0" is consistent with the semantic versioning so this won't likely change. OTOH, maybe the Rubygems folks can offer a better solution. But in any case, we will have to choose a solution now.

@purbon
Copy link
Contributor

purbon commented Nov 26, 2015

@colinsurprenant I made my proposal because using < 2.0.0.a is at my understanding well known and understood. Is more clear than X.999.0 as what happen is "suddenly" we create a version X.1000.0 without wondering, this will break your proposal, but will not break mine. Is very unlikely, at my understanding that pre releases order change.

But I see both proposals as not perfect, so not sure how to make a call on this.

Thoughts?

@colinsurprenant
Copy link
Contributor Author

@purbon version X.1000? are you serious?

For someone looking at at the gemspec, without being aware all the details about this semantic versioning oddity of having 2.0.0.beta1 satifying < 2.0.0, I think it is a lot more obvious what the intent is with having < 1.999.0 compared to < 2.0.0.a. Everyone not aware of the purpose of the .a in < 2.0.0.a will find it strange, while having < 1.999.0, still somewhat weird, clearly convey the meaning of not allowing anything in the 2.x.x range.

If X.999.0 scares you, we can always add another 9? X.9999.0 ? :P

@colinsurprenant
Copy link
Contributor Author

also called the principle of least surprise.

@purbon
Copy link
Contributor

purbon commented Nov 30, 2015

@colinsurprenant Yes I'm serious. Is not that scares me, is that I find it less polite than the other proposal as <2.0.0.a is more accurate showing that is everything lower than 2.0. Having said that both options are equal bad.

@colinsurprenant
Copy link
Contributor Author

sorry @purbon, not equally bad, < 1.999.0 convey the principle of least surprise while < 2.0.0.a does not. you just can't understand why .a is there without understanding this semantic versioning oddity. big difference IMO.

@ph
Copy link
Contributor

ph commented Nov 30, 2015

Been through all this semver discussion, this is what I think.

I would go with the X.999, for the following reason: to understand the meaning of 2.0.0.a , you not only have to understand semver but you also need to understand how the version compare is done internally, inside the dependency manager.

So I believe with your explication, the current dependency string should look something like this:

 Gem::Requirement.new(">= 2.0.0", "< 2.999.0").satisfied_by?(Gem::Version.new("2.1"))

And when we work on the next major release (3.0) we will do something like this:

 Gem::Requirement.new("> 2.999", "< 3.999.0").satisfied_by?(Gem::Version.new("3.0.0.beta1"))

Correct?

@purbon
Copy link
Contributor

purbon commented Nov 30, 2015

@ph thanks for sharing your opinion, so then go for the x.999.0 option. Are we ok with mass updating all core-api-v1 branches?

@jsvd
Copy link
Member

jsvd commented Dec 3, 2015

sorry if I'm late in the game, but what about

jruby-1.7.22 :003 > Gem::Requirement.new(">= 1.4.0", "~> 1").satisfied_by?(Gem::Version.new("2.0.0.beta1"))
 => false 
jruby-1.7.22 :004 > Gem::Requirement.new(">= 1.4.0", "~> 1").satisfied_by?(Gem::Version.new("1.5.5"))
 => true 

@ph
Copy link
Contributor

ph commented Dec 3, 2015

@jsvd This is madness but seems to work.

@purbon
Copy link
Contributor

purbon commented Dec 3, 2015

@jsvd what about pre releases within the 1.x branch? I don't think it will work there.

@ph
Copy link
Contributor

ph commented Dec 3, 2015

@purbon it work.

jruby-1.7.19 :010 > Gem::Requirement.new(">= 1.4.0", "~> 1").satisfied_by?(Gem::Version.new("1.5.6.pre1"))
jruby-1.7.19 :011 > Gem::Requirement.new(">= 1.4.0", "~> 1").satisfied_by?(Gem::Version.new("1.5.6.beta1"))

@jsvd
Copy link
Member

jsvd commented Dec 3, 2015

madness?

madness?

jruby-1.7.22 :007 > Gem::Requirement.new(">= 1.4.0", "~> 1").satisfied_by?(Gem::Version.new("1.1414914.34234234.beta2"))
 => true 

@purbon
Copy link
Contributor

purbon commented Dec 3, 2015

then good, I like this approach @jsvd. @colinsurprenant what do you think?

@colinsurprenant
Copy link
Contributor Author

@jsvd dude. if this works, you're da man. But it's not over yet, per previous comment #4236 (comment)

... when there's a ~> constrain, Bundler will not select prereleases in the first place before validating the Rubygems constrain. So, since we have both types of constrains, I don't know how Bundler will deal with it... 😱

So this need to be validated first to see if it works in the context of Bundler.

@jsvd
Copy link
Member

jsvd commented Dec 4, 2015

@colinsurprenant we can simulate this rather easily, but I'm not sure I understand the scenario where bundler will fail, can you provide a reduced example?

@colinsurprenant
Copy link
Contributor Author

@jsvd it is pretty simple: when using ~> Bundler goes and select versions that are eligible but this selection process excludes pre-releases before the version constrain is verified using Gem::Requirement. So even if Gem::Requirement says that the constrains are satisfied, the pre-release version will not even have been selected bu Bundler in the first place.

Now this is true with a simple ~>, maybe that will change if using both ~> and >= in the same constrain?

@guyboertje
Copy link
Contributor

I vote for < 2.0.0.alpha0

Its clear to all that we want the very last version before we started any kind of release of v 2

@colinsurprenant
Copy link
Contributor Author

@purbon will hate me for this but I do prefer 2.0.0.alpha0 over 2.0.0.a if ^^ @jsvd solution does not work.

@guyboertje
Copy link
Contributor

@jsvd 's solution may work but nobody understands how or why.

@jsvd
Copy link
Member

jsvd commented Dec 4, 2015

If I find the time this weekend I'd like to test this in a local gem server. It seems bundler behaviour is a bug, no?

Any suggestions on the Gemfile/gems setup to test?

My idea is a gem server with
gemA versions 1.0.0, 1.5.0.beta1 and 2.0.0
gemB version 1.0.0 that depends on gemA >= 1.1, ~> 1

Gemfile
gem "gemA"
gem "gemB"

Gemfile.lock
gemA = 1.5.0.beta1

Running bundle install should run ok.

Is this an fair example of what should work/not work @colinsurprenant?

@colinsurprenant
Copy link
Contributor Author

I don't think there is any bug involved per-se

  • I believe that excluding pre-release versions when using ~> effectively complies to the least surprise principle.
  • having x.y.z.pre < x.y.z is defined by / conforms to semantic versioning rules

@jsvd ^^ in your scenario you also need to have a gemA version 2.0.0.alpha1 and it should not be installed in the bundle install

@colinsurprenant
Copy link
Contributor Author

good weekend and good luck 😈

@purbon
Copy link
Contributor

purbon commented Dec 7, 2015

@guyboertje I like you proposal at #4236 (comment), is an improvement over mine, I think is a well known and understood contrain that there is nothing before alpha state and if taking into account alfabetic order it works like charm.

@guyboertje @jsvd @colinsurprenant @ph agreement? so we can move forward with backports? and unblock some that has been waiting for this?

@jsvd
Copy link
Member

jsvd commented Dec 7, 2015

I created 2 gems for this test which I served locally with geminabox:

Gem hola-1.0.0  (no deps)
Gem hola-1.5.0.beta1 (no deps)
Gem hola-2.0.0.beta1 (no deps)
Gem hola-dep-0.9.0
  hola (< 1.9999.0, >= 1.0)
Gem hola-dep-1.0.0
  hola (> 1.2, ~> 1)
Gem hola-dep-1.0.1
  hola (< 1.9999.0, > 1.2)

So hola-dep depends on hola (sorry for the terrible names)

hola-dep 0.9.0 in Gemfile, no Gemfile.lock ✅

% cat Gemfile
source "http://localhost:9292"

gem "hola"
gem "hola-dep" , "0.9.0"
% bundle install
Fetching gem metadata from http://localhost:9292/..
Fetching version metadata from http://localhost:9292/.
Resolving dependencies...
Using hola 1.0.0
Using hola-dep 0.9.0
Using bundler 1.10.6
Bundle complete! 2 Gemfile dependencies, 3 gems now installed.
Use `bundle show [gemname]` to see where a bundled gem is installed.

hola-dep 1.0.0 in Gemfile, no Gemfile.lock ❌

% cat Gemfile
source "http://localhost:9292"

gem "hola"
gem "hola-dep" , "1.0.0"
% bundle install
Fetching gem metadata from http://localhost:9292/..
Fetching version metadata from http://localhost:9292/.
Resolving dependencies...
Bundler could not find compatible versions for gem "hola":
  In Gemfile:
    hola-dep (= 1.0.0) ruby depends on
      hola (> 1.2, ~> 1) ruby

    hola (>= 0) ruby

hola-dep 1.0.1 in Gemfile, no Gemfile.lock ❌

% cat Gemfile
source "http://localhost:9292"

gem "hola"
gem "hola-dep" , "1.0.1"
% bundle install
Fetching gem metadata from http://localhost:9292/..
Fetching version metadata from http://localhost:9292/.
Resolving dependencies...
Bundler could not find compatible versions for gem "hola":
  In Gemfile:
    hola-dep (= 1.0.1) ruby depends on
      hola (< 1.9999.0, > 1.2) ruby

    hola (>= 0) ruby

hola-dep 0.9.0 in Gemfile, hola-dep 0.9.0 Gemfile.lock ✅

% cat Gemfile
source "http://localhost:9292"

gem "hola"
gem "hola-dep" , "0.9.0"
% cat Gemfile.lock
GEM
  remote: http://localhost:9292/
  specs:
    hola (1.0.0)
    hola-dep (0.9.0)
      hola (>= 1.0, < 1.9999.0)

PLATFORMS
  ruby

DEPENDENCIES
  hola
  hola-dep (= 0.9.0)

BUNDLED WITH
   1.10.6
% bundle install
Using hola 1.0.0
Using hola-dep 0.9.0
Using bundler 1.10.6
Bundle complete! 2 Gemfile dependencies, 3 gems now installed.
Use `bundle show [gemname]` to see where a bundled gem is installed.

hola-dep 1.0.0 in Gemfile, manually created Gemfile.lock with hola 1.5.0.beta1 ✅

% cat Gemfile
source "http://localhost:9292"

gem "hola"
gem "hola-dep" , "1.0.0"
% cat Gemfile.lock
GEM
  remote: http://localhost:9292/
  specs:
    hola (1.5.0.beta1)
    hola-dep (1.0.0)
      hola (~> 1, >= 1.2)

PLATFORMS
  ruby

DEPENDENCIES
  hola
  hola-dep (= 1.0.0)

BUNDLED WITH
   1.10.6
% bundle install
Using hola 1.5.0.beta1
Using hola-dep 1.0.0
Using bundler 1.10.6

hola-dep 1.0.1 in Gemfile, manually created Gemfile.lock with hola 1.5.0.beta1 ✅

% cat Gemfile
source "http://localhost:9292"

gem "hola"
gem "hola-dep" , "1.0.1"
% cat Gemfile.lock
GEM
  remote: http://localhost:9292/
  specs:
    hola (1.5.0.beta1)
    hola-dep (1.0.1)
      hola (< 1.999, >= 1.2)

PLATFORMS
  ruby

DEPENDENCIES
  hola
  hola-dep (= 1.0.1)

BUNDLED WITH
   1.10.6
% bundle install
Using hola 1.5.0.beta1
Using hola-dep 1.0.1
Using bundler 1.10.6
Bundle complete! 2 Gemfile dependencies, 3 gems now installed.
Use `bundle show [gemname]` to see where a bundled gem is installed.

I might be missing some test here but I don't see a difference in using < 1.999 and ~> 1

@colinsurprenant
Copy link
Contributor Author

is there a case where it doesn't work when manually editing the Gemfile.lock? In particular the simple ~> 1 should not work? can we make sure it doesn't in fact?

Gem hola-dep-1.0.0
  hola (~> 1)

@jsvd
Copy link
Member

jsvd commented Dec 7, 2015

created:

Gem hola-dep-1.0.2
  hola (~> 1)

works ✅

% cat Gemfile
source "http://localhost:9292"

gem "hola"
gem "hola-dep" , "1.0.2"
% cat Gemfile.lock
GEM
  remote: http://localhost:9292/
  specs:
    hola (1.5.0.beta1)
    hola-dep (1.0.2)
      hola (~> 1)

PLATFORMS
  ruby

DEPENDENCIES
  hola
  hola-dep (= 1.0.2)

BUNDLED WITH
   1.10.6
% bundle install
Using hola 1.5.0.beta1
Using hola-dep 1.0.2
Using bundler 1.10.6
Bundle complete! 2 Gemfile dependencies, 3 gems now installed.
Use `bundle show [gemname]` to see where a bundled gem is installed.

@jsvd
Copy link
Member

jsvd commented Dec 7, 2015

I should note that I'm not that familiar with Gemfile / Gemfile.lock shenanigans, so I might be doing the wrong test here.
from what I see, as long as you have the pre release explicitly in the Gemfile.lock or the Gemfile it works, otherwise it doesn't, regardless of > < = ~>

@colinsurprenant
Copy link
Contributor Author

hm. now I am confused. 😕

@ph
Copy link
Contributor

ph commented Dec 7, 2015

@jsvd Yes this is the same behavior I had and I believe this is consistent with bundler and his gemfile handling.

If you don't explicit set a pre-release version it should be ignored, so this might be the way to go, if we want to explicitly use a pre-release we have to define it in one of the Gemfile.

We could always have a way to update our jenkins jobs that modify the plugin gemfile to test our pre-releases, this would also reduce the number of mass update we have to do on the plugins.

Or maybe event better, why we don't add a job that test the plugins with the current master.

@purbon
Copy link
Contributor

purbon commented Dec 7, 2015

@jsvd I don't think you do a bad test, bundler actually installs the pre releases if you have them explicit in your Gemfile. as @ph also said (3 second before me 👍 jejeje)

@ph
Copy link
Contributor

ph commented May 12, 2016

We can close this, we have decided to generate a logstash-core-plugin-api that will act as the contract between the plugin and the gems. Each time we release a new version of logstash we publish a contract gem that expand the logstash-core constraints.

@ph ph closed this as completed May 12, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants