#784 discourage using globs for shared libraries
Closed: accepted 6 years ago by tibbs. Opened 6 years ago by decathorpe.

I suggest that a small section be added at the top of the Shared Libraries section of the Packaging Guidelines:

Explanation

It has become a frequent occurrence that updates which break dependencies by introducing an soname bump to shared libraries are pushed to the repositories - it happens regularly on rawhide (and sometimes even on stable branches, see https://bodhi.fedoraproject.org/updates/FEDORA-2018-4197fff086 for a recent example).

Almost always, the root cause of this oversight can be traced back to the usage of globs for the relevant entries in the %files section of the package's spec file. That is why I suggest to ban the usage of globs for the specific case where they conceal soname version changes.

In addition to the broken dependencies caused by pushing those "faulty" updates, it violates the Updates Policy for rawhide (which requires one week's notice for soname bumps that affect other packages), and stable releases (where major updates which break API or ABI must be avoided, "if at all possible").

Draft of the addition (updated)

I suggest to add this small section between the introductory paragraph of "Shared Libraries" and the "Downstream .so name versioning" section.

Packaging of shared libraries

If a package contains a shared library, which is meant to be linked to by other programs, and which is installed to %{_libdir} directly, those files SHOULD NOT be listed in the %files section of the spec by using a glob in a way that conceals important parts of the soname (e.g. libfoo.so.*).
Otherwise, when the library bumps its soname as part of an update, this change might remain unnoticed and cause problems like broken dependencies (see the relevant Updates Policy section for further information).
However, if the use of globs is deemed useful by the packager - for example, if minor or micro soname versions change frequently, using something like libfoo.so.11.* is recommended instead, since dependent packages don't have to be rebuilt for bumps of the minor or micro soname version.

For example, if package foo installs the files libfoo.so, libfoo.so.11, and libfoo.so.11.0.2 into %{_libdir}:

# This is not good:
%package libs
%{_libdir}/libfoo.so.*

%package devel
%{_libdir}/libfoo.so

# Do this instead:
%package libs
%{_libdir}/libfoo.so.11
%{_libdir}/libfoo.so.11.*

%package devel
%{_libdir}/libfoo.so

This is a good idea. I would add an example: don't do this, do this instead.

I don't know about this. On one hand, it can help packagers to notice a particular kind of change. On the other hand, it seems that this is a strong prohibition against something which isn't actually wrong in any meaningful way. It's basically demanding that packagers complicate their %files lists in order to force them to notice a change that arguably could be noticed elsewhere in our tooling.

So... I'm ambivalent.

Also, how would you enforce this, or find packages which violate it? Manual inspection doesn't really work, so it would be good to have a check but I'm not sure it's easy to just grep specfiles for this kind of thing.

Edit: Also, I've seen various attempts over the years to try and get packagers to cut down on the use of globs in file lists, for reasons in the same vein. There's a balance to be struck and my opinion has generally been that we should lean towards simplicity of packaging. But if that was the only thing we cared about, then this would be acceptable:

%files
/*

So there's obviously some acceptable middle ground to be found.

So, I hacked together a small script that checks all rawhide .spec files for glob-concealed soname versions for shared libraries.

It found multiple occurrences of each of these (so I think the regular expression I used works as intended).

%{_libdir}/*.so.*
%{_libdir}/*so.*
%{_libdir}/*.so*
%{_libdir}/lib*.so.*
%{_libdir}/lib*.so*

In total, 697 packages use one of these statements.

The complete list of packages, which matching line its spec contains - and at which line number, can be found in this github gist:
https://gist.github.com/decathorpe/f04c4863a3472957eb3b3e9dfb9fd502

I pushed the script that generated the report to a pagure repository. It can generate machine-readable reports in JSON format, too.

https://pagure.io/fedora-random-scripts/blob/master/f/spec-glob-search.py

Pull requests with improvements are welcome.

To explain my reasoning why those globs shouldn't be used better:

I don't want to put additional burden on package maintainers - instead, I want to reduce unnecessary and unexpected work. When not using globs, soname changes can never be a surprise - and that is exactly what I want to achieve: A smaller chance for accidental mistakes.

By the way, all the "unannounced soname bump"s from the last few weeks are included in the list I linked above (LibRaw, mutter, gnome-desktop, soundtouch, brotli, etc.).

So to explain the other side. Most people complain about rpm because they have to do what they consider "busywork" when packaging ... and the more of this they see the loader they complain, until they don't complain at all because they don't do it. While I 100% understand your goals, and experienced packagers might well just shrug and go with it, I think it's very likely less experienced packagers will think it's just busywork.

So assuming we don't want to just give up and push for some kind of autoQA thing that does something when SOnames have changed, Eg. email people about rawhide ... puts something on bodhi updates ... I would suggest creating some kind of rpm macro that can be used like:

# We are matching
# /usr/lib64/libgnome-desktop-3.so.12
# /usr/lib64/libgnome-desktop-3.so.12.2.0
# and instead of:
# %{_libdir}/lib*.so.*
# we do:
%_libsoname(12)
# or maybe...
# %_libsoname(gnome-desktop-3, 12)

A macro would be nice indeed. It should take the lib name (%_libsoname(gnome-desktop-3, 12)).
There should also be a matching _libsoname_devel() that would go into %files devel.

I think MUST is too strong. There are many packages where upstream is very careful about so-name changes, and intends forever to remain at .0, and an "unnoticed bump" is very unlikely. (systemd is an example, with libsystemd being now at 0.19.0). Those packages don't benefit from this. But if the guidelines is changed to MUST, packages will need to adjust or be non-compliant. So I think it's better to make this a recommended practice, put an example in the guidelines, and add the macro proposed in previous comment, but not make it mandatory.

What do you think about changing it to:

(...) SHOULD NOT be listed in the %files section of the spec by using a glob in a way that conceals the major soname version (e.g. libfoo.so.*) (...)

I'm open about adding a macro for that, but I think it would just clutter the %files list and introduce a new error source. Not allowing globs for the soname version (or parts of it) is a really small change in comparison, and there's no new macro to learn for packagers (so I disagree about the "burden for entry" argument here).

IMHO, this is a very bad guideline, because it increases work for packagers, when we are trying to make it easier.

I would think this solution is a good trade-off between slightly more work during the preparation of updates and much less work cleaning up after unintended soname bumps.

I still think the idea is good but not always applicable and should be a recommendation only. With emphasis on MUST not bump soname unannounced -> should not use globs that conceal soname version unless absolutely sure a soname bump would not go by accident.

As for the macro I think that one makes it needlessly more complicated and thus is not an improvement.

A well crafted 'find' script can build a manifest of such files. Such were used regularly in perl module packaging if one needs an example. Put it into a macro if one wishes. Easy enough to add versioned naming it with a NEVR name

By providing a rule permitting retaining the most recent three or four of such, and adding a (macro driven?) build process to enumerate a current build's result and diff it against one of more of the prior examples, we can detect and warn / error out during the build process. To work around the check, simply delete any prior comparands

I would suggest this manifest be automatically built, and carried as a %doc type file, so a glob retaining such may be used trivially by rolling into an updatable %SOURCEn file in a post process. Alternatively it may be done manually of course

-- Russ herrold

I strongly support this guideline, since it's the most practical way to solve the problem of unannounced soname bumps. I was actually thinking about proposing it myself, it's such an obvious solution to the unannounced soname bump problem.

But remember not all usage of globs is problematic. Of course libgnome-desktop-3.so.* should be banned, since that hides soname bumps. But we wound up using libgnome-desktop-3.so.17{,.*} which is fine since it allows us to avoid build failures for every minor bump, but still fail if the major version changes. That won't work for all packages, but it will work for all that follow the extremely-common libtool versioning convention. Without that glob, we'd be having build failures on almost every package update, and that would be unworkable.

Metadata Update from @tibbs:
- Issue tagged with: hasdraft, meeting

6 years ago

The problem here is that if we can't specify clearly what is allowed and what is not, then we're left with either creating a vague guidelines that becomes a source of confusion, or just not writing a guideline at all. And even a well-specified guideline will still miss cases we haven't thought about.

The above example about minor version bumps is just one thing we have to be careful about. The draft says that you can't use a glob which "conceals the soname version" but I don't honestly know if it would allow or ban the use of libgnome-desktop-3.so.17{,.*}.

I don't have a solution here. I think the proposed guideline, if downgraded to a SHOULD NOT, is almost there but perhaps needs more examples.

way that conceals the soname version (e.g. libfoo.so.*).

Change this to:

way that conceals the soname major version. Thus e.g. %_libdir/libfoo.so.* SHOULD NOT be used, but _libdir/libfoo.so.11{,.*}which matches all minor versions of version 11 of libfoo is OK.

How about:

way that conceals the soname version. For example, if the SONAME is libfoo.so.11 then %_libdir/libfoo.so.* SHOULD NOT be used, but %_libdir/libfoo.so.11{,.*} is OK.

@jwakely the problem with that definition is that it's not precise. The thing that needs to be not globbed is the major part of the major-minor-micro so-suffix, so I think the word "major" simply has to appear somewhere in the text.

Surely that depends on the SONAME?

For Boost the whole version is part of the SONAME, e.g. libboost_system.so.1.64.0, so a glob that did libboost_system.so.1.* would still be wrong, and so would libboost_system.so.1.64.*

For packages that use semantic versioning, where the SONAME is libfoo.MAJOR, a glob that matches libfoo.MAJOR.* is OK, but not all packages follow conventions like that.

That's why I suggested an example that says "if the SONAME is ..." because that is precise. Otherwise you're making assumptions about what parts of the filename correspond to the SONAME.

You can just add this explanatory paragraph:
"The soname version is the version encoded in the DT_SONAME field. Usually, this is only the major version. Minor version numbers not included in the DT_SONAME field can be safely globbed."

(Please note that I still think that this is best left to the discretion of the maintainers and not written in a guideline. But with those additions, the guideline actually makes sense, at least.)

@zbyszek That's why I adapted my proposal to include the "major" distinction in comment https://pagure.io/packaging-committee/issue/784#comment-522874

So, the current complete draft, including some more information), is:

Packaging of shared libraries

If a package contains a shared library, which is meant to be linked to by other programs, and which is installed to %{_libdir} directly, those files SHOULD NOT be listed in the %files section of the spec by using a glob in a way that conceals the major soname version (e.g. libfoo.so.*).
Otherwise, when the library bumps its soname as part of an update, this change might remain unnoticed and cause problems like broken dependencies (see the relevant Updates Policy section for further information).
However, if the use of globs is deemed useful by the packager - for example, if minor or micro soname versions change frequently, using something like libfoo.so.11.* is recommended instead, since dependent packages don't have to be rebuilt for bumps of the minor or micro soname version.

Please suggest further improvements, especially in regards to clarity and style (English is not my first language).

@decathorpe that still has the text "major soname version" and I don't know what that means. The soname is the soname. The soname is the DT_SONAME tag, not the filename.

@kkofler's suggestion is good.

Ah, I didn't see @kkofler 's comment before I posted my latest comment. I'm not familiar enough with the technical details, but including this as an explanatory paragraph is fine with me.

And what I mean by "major soname version" is the X part of libfoo.so.X.Y.Z. If that's not the correct term, please tell me what is.

The soname is the DT_SONAME tag in the library, which by convention also appears in the filename, but that is not required. You can have a file called libfoo.so.1 with a SONAME of libbar.so.2.3 so any talk of "major soname version" in filenames is inaccurate and IMHO misleading.

Also, is "micro" the conventional term here? I'm used to MAJOR.MINOR.PATCH not micro.

I think the following proposal is more accurate, but has some redundancy that could be removed:

If a package contains a shared library, which is meant to be linked to by other programs, and which is installed to %{_libdir} directly, those files SHOULD NOT be listed in the %files section of the spec by using a glob in a way that conceals the soname version.
Note: The soname version is the version encoded in the DT_SONAME field. Usually, this is only the major version. Minor version numbers not included in the DT_SONAME field can be safely globbed.
Otherwise, when the library bumps its soname as part of an update, this change might remain unnoticed and cause problems like broken dependencies (see the relevant Updates Policy section for further information).
However, if the use of globs is deemed useful by the packager - for example, to match minor or micro versions that are not part of the soname, using something like libfoo.so.11.* is recommended instead, since dependent packages don't have to be rebuilt for bumps in minor version numbers that do not affect the soname version.
Example: if the SONAME is libfoo.so.11 then %_libdir/libfoo.so.* SHOULD NOT be used, but %_libdir/libfoo.so.11{,.*} is OK.

I think the terminology confusion is partly due to build systems, libtool in particular, using their own terminology. libtool is where that concept of "major version" comes from.

In CMake, VERSION is the complete version and SOVERSION is the soname version meant here. They can be entirely different (as in @jwakely's example: VERSION "1" SOVERSION "2.3").

Other build systems may use different terminology.

Ugh. Okay, I guess this is more complicated than I thought (no wonder, since build systems are involved).

However, I don't have the required in-depth technical knowledge to deal with this (and I don't think most packagers do either), so why not go up one level of abstraction, instead of down?

What about something like: "don't use globs for file names or parts thereof, when a change in the matched part of the file name requires dependent packages to be rebuilt"?

What about something like: "don't use globs for file names or parts thereof, when a change in the matched part of the file name requires dependent packages to be rebuilt"?

Changes in the file name do not require rebuilds, changes in the soname require rebuilds. In the hypothetical (and unusual) case where the file libfoo.so.1 has soname libbar.so.2.3 you could change the file name to libfoo.so.2 or libbaz.so.9 and it wouldn't require a rebuild if the soname stays the same.

That is why I chose these words carefully:

to match minor or micro versions that are not part of the soname, using something like libfoo.so.11.* is recommended instead, since dependent packages don't have to be rebuilt for bumps in minor version numbers that do not affect the soname version.

Discussed this week: https://meetbot-raw.fedoraproject.org/fedora-meeting-1/2018-07-26/fpc.2018-07-26-16.00.txt

  • 784 forbid globs for shared libraries as it conceals sonames
    (geppetto, 16:05:36)
  • LINK: https://pagure.io/packaging-committee/issue/784 (tibbs,
    16:06:45)
  • ACTION: forbid globs for shared libraries as it conceals sonames
    (+1:6, 0:0, -1:0) (geppetto, 16:32:53)
  • ACTION: Need examples added to the proposal voted on. (geppetto,
    16:33:17)

I updated the original draft with hopefully "more technically correct" and "less objectionable" wording, and added a small example of what would be considered "not good", and what to do instead. Further improvements by an English native speaker might be necessary and are appreciated.

I still don't know what "minor or micro soname version" means. The soname is the soname. There is no subdivision into minor and major parts, just the soname, as a whole.

As I said in https://pagure.io/packaging-committee/issue/784#comment-523675 I chose the words very carefully when I said "to match minor or micro versions that are not part of the soname" and "bumps in minor version numbers that do not affect the soname version".

But I'm not going to keep flogging this horse, I don't seem to be adding anything constructive here.

I still don't know what "minor or micro soname version" means. The soname is the soname. There is no subdivision into minor and major parts, just the soname, as a whole.

That's why I was asking for help, since I don't know the correct terminology.

As I said in https://pagure.io/packaging-committee/issue/784#comment-523675 I chose the words very carefully when I said "to match minor or micro versions that are not part of the soname" and "bumps in minor version numbers that do not affect the soname version".

Yes, I know now that the soname version of a library is a different thing from the last part of the file name. However, John/Jane Average Paverage packager won't know that - as neither did I, before opening this issue. That's why I'm looking for a technically correct, but easily understood wording.

But I'm not going to keep flogging this horse, I don't seem to be adding anything constructive here.

You're input is valued, I'm just trying to find a good compromise between mentioning too many technicalities and being too abstract.

@jwakely since we moved guidelines to this repo, would you like to send pull request for this ticket?

Thanks!

@ignatenkobrain I'll do it, it's my proposal after all. We can discuss the specific wording on the pull request, if any more changes will be necessary.

We talked about this in this weeks meeting (https://meetbot-raw.fedoraproject.org/fedora-meeting-1/2018-10-04/fpc.2018-10-04-16.00.txt):

Metadata Update from @james:
- Issue untagged with: hasdraft, meeting
- Issue tagged with: announce

6 years ago

Draft of announcement text:

Due to the recent accumulation of problems caused by unannounced SONAME bumps in rawhide, a short "Listing shared library files" section was added to the "Shared Libraries" chapter of the Packaging Guidelines: It is now discouraged to use globs of the form libFOO.so.* (or similar) for listing %files entries like libFOO.so.MAJOR.MINOR.PATCH in %{_libdir}, because it can conceal SONAME changes, and may contribute to accidental bumps. If the use of any glob is helpful in reducing maintenance burden, using something less general - like libFOO.so.MAJOR* - is encouraged instead.

Feel free to edit as you like.

Metadata Update from @tibbs:
- Issue untagged with: announce
- Issue close_status updated to: accepted
- Issue priority set to: None (was: Needs Review)
- Issue status updated to: Closed (was: Open)

6 years ago

Log in to comment on this ticket.

Metadata