openSUSE:Migrating to libalternatives with alts
Migrating to libalternatives by leveraging the alts package
There are multiple ways to migrate to libalternatives:
- Using the
altsbinary as a dependency, then symlinking the packageâs binary to%{_bindir}/alts. - Creating a custom binary written in C and linking said binary to
libalternatives.so.
Weâll discuss approach #1 here.
Goal
Rather than describing the perfect sequence of steps that will guide you to migrate your package, weâll describe the end goal.
That way, even if some of the steps described here donât perfectly apply to your package, youâll still have a clear mental model of what the migration is trying to achieve. Adapt as needed.
- There should be a symlink linking the common/shared executable to
/usr/bin/alts. For example, if we were packagingjava-17-openjdkandjava-18-openjdk, then weâd have a third packagejava-openjdk-commonthat would provide/contain/usr/bin/java. That file would be a symlink to/usr/bin/alts.java-17-openjdkandjava-18-openjdkwould then requirejava-openjdk-common, making it a strict dependency. - Each package providing its respective âalternativeâ should NOT provide this executable. Instead, they will provide their unique executable. Thatâs why they would depend on
java-openjdk-common. For example, the actual binary for packagejava-17-openjdkwould be/usr/lib64/jvm/jre-17-openjdk/bin/java. - Each package providing its respective alternative will also provide its unique configuration file for
libalternatives. This is the file that is read by/usr/bin/alts, when it decides how to callexec(). It also denotes the alternativeâs priority. - The man page should also be unique, for each alternative (no conflicting files). Which man page is displayed when
manis invoked is decided by themanexecutable itself. Our openSUSE/SLE version has been patched to follow the same decision that/usr/bin/altsmakes when deciding which executable to invoke. That way, the alternative decision applies to both the executable, and its man page.
Letâs get started.
Common package: create it and add alts as a dependency
The runtime dependency on /usr/bin/alts is required because we donât want to break that symlinkâs target. It should be present at all times.
This dependency should be part of the common package. For instance, the aforementioned java-openjdk-common (which doesnât exist, itâs just an example).
As every alternative package should have a dependency on the common package, they would pull /usr/bin/alts through the indirect relationship.
In the case of Transmission, we introduced the transmission-common package. Here is an excerpt:
Package: transmission-common Requires: alts
Common package: set up the symlink to /usr/bin/alts and create the libalternatives configuration directory
The %install phase for the common package should create the symlink to /usr/bin/alts, and create the configuration directory for libalternatives, where all the priority files will be placed.
If youâre using a single spec file to create multiple packages, of course, there will still only be one %install section.
But in any case, both the symlink and the directory will still be owned by the common package.
%install
# alternatives - create binary symlink
ln -s -f %{_bindir}/alts %{buildroot}%{_bindir}/%{name}
# alternatives - create libalternatives configuration directory
mkdir -p %{buildroot}%{_datadir}/libalternatives/%{name}
Seeing as %{name} in this case would expand to transmission, this would be the result. The directory is named after the whole alternative group.
# PARTIALLY MACRO-EXPANDED (except for %{buildroot})
%install
# alternatives - create binary symlink
ln -s -f /usr/bin/alts %{buildroot}/usr/bin/transmission
# alternatives - create libalternatives configuration directory
mkdir -p %{buildroot}/usr/share/libalternatives/transmission
Alternative packages: declare the dependency on the common package
For each alternative package, we must declare a dependency on the common package.
In the case of transmission, these are the packages transmission-cli, transmission-gtk, transmission-qt.
The following is an excerpt for how the dependency would look like:
Package: transmission-cli Requires: transmission-common Package: transmission-gtk Requires: transmission-common Package: transmission-qt Requires: transmission-common
Alternative packages: create the libalternatives configuration files
Each alternative package will have its own libalternatives configuration file.
This configuration file serves a few purposes:
- It indicates which actual executable should be invoked, once
altsis called through the common/shared symlink (/usr/bin/transmission, for instance). - It indicates which set of man pages relates to this particular alternative (
/usr/share/man/man1/transmission-gtk.1.gzrelates to/usr/bin/transmission-gtk). - It indicates that alternativeâs priority.
Therefore, for each alternative package, we need to create a custom configuration file.
Below is an example.
5 is the priority here, because the file is named 5.conf. This priority relates to the priority 5 that transmission-cli had when it was still set up using update-alternatives.
Bear in mind that the directory name is shared across all alternatives. So here, the directory is /usr/share/libalternatives/transmission.
This point was mentioned before, but itâs good to bear it in mind.
%install
# alternatives - create 'cli' configuration file
cat > %{buildroot}%{_datadir}/libalternatives/%{name}/5.conf <<EOF
binary=%{_bindir}/%{name}-cli
man=%{name}-cli.1
EOF
And this would be the example for transmission-gtk, priority 15:
%install
# alternatives - create 'gtk' configuration file
cat > %{buildroot}%{_datadir}/libalternatives/%{name}/15.conf <<EOF
binary=%{_bindir}/%{name}-gtk
man=%{name}-gtk.1
EOF
Make sure the correct packages own the correct files
As we mentioned earlier, the main executable symlink is owned by the common package. And so is the libalternatives configuration file directory, for the whole group of alternatives this package represents.
%files common
%{_bindir}/%{name} # symlink: /usr/bin/transmission -> /usr/bin/alts
%dir %{_datadir}/libalternatives/%{name} # libalternatives config dir: /usr/share/libalternatives/transmission
As for each alternative package, they should own their respective executables, libalternatives configuration file, and man page.
%files gtk
%{_bindir}/%{name}-gtk # executable: /usr/bin/transmission-gtk
%{_mandir}/man1/%{name}-gtk.1%{?ext_man} # man page: /usr/share/man/man1/transmission-gtk.1.gz
%{_datadir}/libalternatives/%{name}/15.conf # libalternatives config file: /usr/share/libalternatives/transmission/15.conf
This should be it
If you go back and check our original goals when migrating the package, they shouldâve been accomplished by now.