Mir
A brief guide for versioning symbols in the Mir DSOs

So, what do I have to do?

There are more detailed descriptions below, but as a general rule:

Can I have some details?

Sure.

Mir is a set of libraries, one C++ library for writing display- server/compositor/shells and one C library for writing clients (or, more usually, toolkits for clients) that use a Mir display-server for output. Mir also has internal dynamic libraries for platform support - drivers - and may in future allow the same with extensions to the core functionality. As such, the ABI of these interfaces is important to keep in mind.

Mir uses the ELF symbol versioning support. This provides three advantages:

When should I bump SONAME?

There are varying standards for when to bump SONAME. In Mir we choose to bump the SONAME of a library whenever we make a change that could cause a binary linked to the library to fail as long as the binary is using only public interfaces and (where applicable) relying on documented behaviour. In general, changes that make an interface work as described by its documentation will not result in SONAME bumps.

With that explanation, you should bump SONAME when:

If you are changing the behaviour of an interface, think about whether it's easy to maintain the old interface in parallel. If it is, you should consider providing both under different versions. This should become easier over time as the Mir ABI becomes more stable and also more valuable over time as the Mir libraries become more widely used.

Load-time version detection

When using versioned symbols the linker adds an extra, special symbol containing the version(s) exported from the library. Consumers of the library resolve this on library load. For example:

$ objdump -C -T lib/libmirclient.so
…
00000000002a2080  w   DO .data.rel.ro   0000000000000080  MIR_CLIENT_8 vtable for mir::client::DefaultConnectionConfiguration
0000000000000000 g    DO *ABS*  0000000000000000  MIR_CLIENT_8 MIR_CLIENT_8
0000000000030ed2 g    DF .text  0000000000000098  MIR_CLIENT_8 mir::client::DefaultConnectionConfiguration::the_rpc_report()
…

This shows the special MIR_CLIENT_8 symbol of the current libmirclient, along with a versioned symbol in the read-only data segment (the vtable for mir::client::DefaultConnectionConfiguration) and a versioned symbol in the text segment (the implementation of mir::client::DefaultConnectionConfiguration::the_rpc_report()). If a client needed a symbol versioned with MIR_CLIENT_9, it would try to resolve this at load time and fail, rather than failing when the symbol was first referenced - possibly much later, and more confusingly.

So what do I have to do to make this work?

When you add new symbols, add them to a new version block in the relevant symbols.map file, like so:

MIR_CLIENT_0.17 {
    global:
        mir_connect_sync;
        ...
        /* Other symbols go here */
};

MIR_CLIENT_0.18 {
    global:
        mir_connect_new_symbol;
    local:
        *;
} MIR_CLIENT_0.17;

Note that the script is read top to bottom; wildcards are greedily bound when first encountered, so to avoid surprises you should only have a wildcard in the final stanza.