API and ABI backward compatibility guarantees

Dear all,

Most libraries provide strong API and ABI backward compatibility guarantees: any program compiled against version 1.A is guaranteed to work with any version >1.A and <2.0. ROOT doesn’t seem to have any API or ABI backward compatibility guarantee. As an example, consider the signature of TH1::FindFirstBinAbove. In ROOT 6.16, it is int (double, int) const while it is int (double, int, int, int) const in ROOT 6.18. This is an API breaking change since the type of the expression &TH1::FindFirstBinAbove has changed. This is also an obvious ABI breaking change (that could have been avoided by keeping a two-arguments overload). A more subtle example of an ABI breaking change is the defaulting of TLine::TLine().

This brings me to my question: What are ROOT’s guarantees for API and ABI compatibility? (Is this documented somewhere?) If there is any kind of guarantee, I need to report the above as bugs; otherwise, I guess I need to recompile any ROOT-dependent code every time the framework is updated (which would be a very good reason to limit the amount of such code to a minimum).

Thank you,
Louis.

Each time you change the ROOT version that you use, you must rebuild all your libraries and executables which depend on it.

I’m afraid this is a known deficiency. Eons ago, when I complained about it to Rene Brun (the founder of this project), I got the reply that it’s because “ROOT is not a set of libraries but a framework”.

Thank you for your quick answer. I wish the developers would have taken other decisions, but that’s how it is. Every ROOT update is going to be a potential pain.

Hi,

That’s very simply a question of resources and where to put them:

Maintaining an API and ABI compatible library is very expensive: any change needs to be validated against potentially breaking API or ABI compatibility. Additional overloads need to be injected / mechanisms need to be put in place (e.g. API versioning, e.g. through parameter struct size), resulting in even higher maintenance burden and uglier / opaque interfaces.

What ROOT guarantees are two compatibility elements:

  • source backward compatibility. You can expect that any breakage to existing code should be called out in the release notes. In your example, TH1::FindFirstBinAbove must still be callable by passing (1, 12). If not, if must appear in the release notes. If not, please report that as a bug. In the case of TH1::FindFirstBinAbove, the two new extra parameters are defaulted, allowing code to remain unchanged.
  • I/O backward and forward compatibility: any file written by older ROOT versions must be readable by newer ROOT versions, and even: any file written by newer ROOT versions should be readable by older ROOT versions. (More precisely, because of the recent introduction of new compression algorithms: by the latest release of any of the active previous ROOT versions.) That’s like MS Word 1995 being able to read MS Word 2019 files :wink:

Given HEP’s limited investment in software it seemed more beneficial for the community to relax the API/ABI compat requirements, enabling cheaper technical evolution and in general investment in other SW areas. But it’s really a conscious choice.

Cheers, Axel.

Thank you Axel. I agree that there is a lack of investment in our software stacks.

Source compatibility for functions like TH1::FirstBinAbove is more than just being able to call it with the same arguments. For example, the following code doesn’t work anymore:

int (TH1::*f)(double, int) const = &TH1::FindFirstBinAbove;

I agree this is a niche use case, but it demonstrates how adding function parameters always breaks the API.

Yup, I’m aware but due to its niche-ness that’s not how we tend to define source-compatibility :slight_smile: Thanks for your understanding!

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.