# Bartleby: a symbol renaming tool.
* [Why?](#why)
* [How?](#how)
* [Example](#how-example)
* [Getting Started](#getting-started)
* [Using CMake](#using-cmake)
* [Using Bazel](#using-bazel)
* [Importing Bartleby in your Bazel project](#bazel-import)
* [License](#license)
This repository contains the source code for Bartleby, a library and a tool
to rename symbols across several object files and static libraries.
## Why?
If you develop a library, your end product will contain:
* definition for symbols that are made available to your users
("exported" functions and so on),
* definition for symbols that you consider to be implementation details of
your library and wish to hide from your users ("private" functions),
* references to symbols that you use in your dependencies (and their
definition if you link statically).
Bartleby helps you ship a static library exposing only the symbols you want
exposed and without polluting the namespace with the symbols of your own
dependencies.
It is similar to `objcopy --prefix-symbols=__private` but with
"intelligent" (see the ["How?"](#how) section below) automated selection of
which symbols to prefix (for instance a reference to puts won't be
renamed `private_puts` because it would lead to unresolved symbols).
This helps solving the diamond dependency problem: if a user of your library
wants to link against OpenSSL, which also happens to be a library you link
against, how do you make sure that the user remains in control of the
version of OpenSSL they use without having name collisions with your own use of
OpenSSL? You use Bartleby.
## How?
Bartleby takes a set of files as input. These can be static libraries (`.a`) or
objects (`.o`). Bartleby figures out which symbols are considered private or not
by iterating over all these objects (the ones directly supplied by the user, or
the ones contained in the archives), and collects the visibility and the
definedness of each symbols found. Using these pieces of information, a map of
symbols is built as follows:
* If a symbol is found in several places across the input files, and at least
one object actually defines it, then the symbol is marked as defined.
* If a symbol found in an object has global visibility, then the symbol is
marked as global.
Finally, for each symbol marked as defined and global, Bartleby considers them
as private and adds a prefix to its name, and rename all its references across
all the objects.
The output is a single static library that contains all the processed objects.
### Example
Let's say we have the following three C files, along with their header:
* `api_1.c`:
```c
#include
#include
__attribute__((visibility("default"))) void my_api(void) {
puts("my_api called, calling external dep");
char buf[0x41];
external_api(buf, sizeof(buf));
}
void internal_impl(void) {
puts("internal implementation, calling `my_api`");
my_api();
}
```
* `api_2.c`
```c
#include
#include "api_1.h"
__attribute__((visibility("default"))) void another_api(void) {
puts("another_api called, calling my_api");
my_api();
}
```
* `external_dep.c`
```c
#include
#include
__attribute__((visibility("default"))) void external_api(void *src, const size_t n) {
puts("external_api called, calling external private impl");
memset(src, 0, n);
}
void external_private_impl(void) {
puts("external private implementation called");
}
```
We compile the first two objects from our API:
```shell
$ clang -fvisibility=hidden -c api_1.c api_2.c -isystem.
$ file api_1.o api_2.o
api_1.o: Mach-O 64-bit object arm64
api_2.o: Mach-O 64-bit object arm64
```
Then, we create a static library out of them:
```shell
$ ar rvs libapi.a api_1.o api_2.o
```
Now, let's build our simple external dependency:
```shell
$ clang -fvisibility=hidden -c external_dep.c
$ ar rvs libexternal.a external_dep.o
```
`libapi.a` is the library we want to have its symbols prefixed with our custom
prefix.
`libexternal.a` is one of our external dependencies, and we want the user to be
able to link against `libapi.a` without encountering any C symbol collisions
because `libexternal` might be used in another place.
We run Bartleby by giving `libapi.a` and `libexternal.a` as an input.
The output will be `libapi_v1.1.a`:
```shell
$ bartleby --if libapi.a \
--if libexternal.a \
--of libapi_v1.1.a \
--prefix __impl_v1.1_
5 symbol(s) prefixed
libapi_v1.1.a produced.
```
Now, we use `nm` to inspect the produced archive:
```shell
$ nm libapi_v1.1.a
pi_1.o:
U ___impl_v1.1_external_api
0000000000000064 T ___impl_v1.1_internal_impl
0000000000000000 T ___impl_v1.1_my_api
U ___stack_chk_fail
U ___stack_chk_guard
U _puts
0000000000000084 r l_.str
00000000000000a8 r l_.str.1
api_2.o:
0000000000000000 T ___impl_v1.1_another_api
U ___impl_v1.1_my_api
U _puts
0000000000000020 r l_.str
external_dep.o:
0000000000000000 T ___impl_v1.1_external_api
000000000000001c T ___impl_v1.1_external_private_impl
U _puts
0000000000000038 r l_.str
000000000000006b r l_.str.1
```
We can now see that defined symbols have been prefixed with `__impl_v1.1`.
However, `puts` hasn’t been prefixed, because even if it’s a global symbol,
no definition has been found in either `libapi.a` or `libexternal.a`, therefore
Bartleby didn’t prefix it.
## Getting Started
Bartleby can be compiled with Bazel or CMake.
### Using CMake
LLVM `>=` 15.0 is required to build Bartleby. See [Getting Started](https://llvm.org/docs/GettingStarted.html)
to find more information about how to build LLVM, and
[LLVM releases](https://releases.llvm.org/) to find all the releases.
APT packages are also available on the [LLVM Debian/Ubuntu packages page](https://apt.llvm.org/).
```shell
$ cmake -B build -DCMAKE_BUILD_TYPE=Release -DLLVM_DIR=/path/to/llvm-15/lib/cmake
$ cmake --build build
$ ./build/bin/bartleby
```
If an higher version of LLVM is installed, `BARTLEBY_LLVM_VERSION` must be defined:
```shell
$ cmake -B build -DCMAKE_BUILD_TYPE=Release -DBARTLEBY_LLVM_VERSION=16.0 -DLLVM_DIR=/path/to/llvm-16/lib/cmake
$ cmake --build build
```
### Using Bazel
It is highly recommended to use [`bazelisk`].
```shell
$ bazelisk build -c opt bartleby/...
$ ./bazel-bin/bartleby/tools/Bartleby/bartleby
```
### Importing Bartleby in your Bazel project
It is also possible to import Bartleby in an existing Bazel workspace using
[`http_archive`].
One can use it as a tool, or can also use it through the [bartleby starlark
rule].
See [`examples/bazel`](/examples/bazel) for more information.
## License
See [`LICENSE`].
[`bazelisk`]: https://github.com/bazelbuild/bazelisk/releases
[`http_archive`]: https://bazel.build/rules/lib/repo/http#http_archive
[bartleby starlark rule]: /docs/rules.md
[`LICENSE`]: /LICENSE