pathikrit/better-files

Binary imcompatibility between 3.7.0 and 3.7.1

35VLG84 opened this issue · 7 comments

Hello,

It seems that better-files versions of 3.7.0 and 3.7.1 are binary imcompatible.

This is manifested when using mixed version of better-files in project (one is pulling 3.7.0 and other 3.7.1):

java.lang.NoSuchMethodError: 
better.files.File.glob$default$3(Ljava/lang/String;Z)Lbetter/files/File$PathMatcherSyntax;

This seems to be caused by PR #290 which added new argument to the glob.

Could it be possible to release 3.7.2 without #290 and 3.8.0 with #290. If this this is too much trouble, then it might be good to add note to the release notes.

I also reviewed the PR and didn't notice this - c'est la vie.

better files considers it a minor version bump if extra default parameters are added (I know it can still break curried usages but in most other cases your code will just still work). Perhaps a better question is how/why you have 2 versions and if you do, why can't you explicitly scope them to pickup the one you actually want?

avdv commented

better files considers it a minor version bump if extra default parameters are added (I know it can still break curried usages but in most other cases your code will just still work)

That's not true. This breaks binary API compatibility in any case. You changed a 2-arity function into a 3-arity one (minus the implicit arg). Once compiled, the JVM wants to call either an 2-arity or 3-arity function.

The default argument only makes it source compatible in regard to the older version since default arguments are handled by the compiler. So you have to recompile your code if you switch from 3.7.0 to 3.7.1.

If you use one library compiled with 3.7.0 and another compiled with 3.7.1 you're in trouble.

To keep binary compatibility you could overload the method instead, without a default argument:

  def globRegex(
      pattern: Regex,
      includePath: Boolean = true
    )(implicit
      visitOptions: File.VisitOptions = File.VisitOptions.default
    ): Iterator[File] = globRegex(pattern, Int.MaxValue, includePath)

  def globRegex(
      pattern: Regex,
      maxDepth: Int,
      includePath: Boolean = true
    )(implicit
      visitOptions: File.VisitOptions = File.VisitOptions.default
    ): Iterator[File] = ...

I see. Unfortunately, I can't do much now besides helping you figure out how you have conflicting versions.

Hi,
Thanks @avdv for explaining exactly the case in question. DirSuite ScalaTest extension is using better-files, and it's compiled with 3.7.0. Now if I bump better-files on main project, then it won't compile anymore, for reason stated above. I could release DirSuite with 3.7.1 but then it won't work with any project which is using better-files <3.7.1 ...

It would be nice to release fixed (binary compatible) version as 3.7.2, and possible non-binary compatible version as 3.8.0 if isn't possible to include that feature in binary compatible way.

In any case, thanks for maintaining better-files - it has been great help in my projects!

avdv commented

Unfortunately, I can't do much now besides helping you figure out how you have conflicting versions.

I think there is a workaround which caters both version and is also source compatible with existing code.

// 1. re-introduce glob with the original signature (3.7.0)
  def glob(
      pattern: String,
      includePath: Boolean = true
    )(implicit
      syntax: File.PathMatcherSyntax = File.PathMatcherSyntax.default,
      visitOptions: File.VisitOptions = File.VisitOptions.default
    ): Iterator[File]

// 2. overload with 3 args but no default arguments (3.7.1)
  def glob(
      pattern: String,
      includePath: Boolean,
      maxDepth: Int
    )(implicit
      syntax: File.PathMatcherSyntax = File.PathMatcherSyntax.default,
      visitOptions: File.VisitOptions = File.VisitOptions.default
    ): Iterator[File]

// 3. overload with maxDepth for source compatibility
  def glob(
      pattern: String,
      maxDepth: Int
    )(implicit
      syntax: File.PathMatcherSyntax = File.PathMatcherSyntax.default,
      visitOptions: File.VisitOptions = File.VisitOptions.default
    ): Iterator[File]

This should fix all possible variations of usages, no matter if you use it with libraries compiled for 3.7.0 or 3.7.1.

// 1. call it with one arg (first method)
file.glob("*.txt")

// 2. call it with two args (first method)
file.glob("*.txt", true)
file.glob("*.txt", includePath = false)

// 3. call it with thre args (second method)
file.glob("*.txt", true, 3)
file.glob("*.txt", maxDepth = 3, includePath = false)

// 4. call it only with maxDepth (third method)
file.glob("*.txt", maxDepth = 3)

v3.8.0 is released and should mitigate this

Awesome, thank you! I will release version of https://github.com/e257-fi/dirsuite for 2.13-RC1 soon.