Skip to content
/ resman Public

Cross-platform resource compiler and manager based on llvm/clang

License

Notifications You must be signed in to change notification settings

nohajc/resman

Repository files navigation

resman

Cross-platform resource compiler and manager based on llvm/clang

Linux macOS Windows
Linux build status macOS build status Windows build status

Table of Contents

About

This is a cross-platform solution for embedding resource files into executables.1

Goals

  • No boilerplate
  • Efficient intermediate representation
  • Universal solution for all common platforms
  • No runtime dependencies

Existing approaches to this problem

  • Using Windows resource management (only available for Windows PE files)
  • Converting your files into C/C++ byte arrays which can be compiled (this can produce very large source files)
  • Using utilities like objcopy (quite low-level, Linux-only AFAIK)
  • Using the Qt Resource System (adds runtime dependency on Qt)

As you can see, none of the above can fulfill all the goals. However, with the help of LLVM and Clang, we can do better.


1 Note that we're talking about embedding during linking phase. This tool is not able to modify existing executables.

How to use

This project consists of a resource compiler rescomp and a header file include/resman.h.

Instead of converting your files into C/C++ byte arrays, you just declare which files you want to embed like this:

resource_list.h

#pragma once
#include "resman.h"

// Define a global variable for each file
// It will be used to refer to the resource
constexpr resman::Resource<1> gRes1("resource_file1.jpg"); // resource with ID 1
constexpr resman::Resource<2> gRes2("resource_file2.txt"); // resource with ID 2
constexpr resman::Resource<3> gRes3("resource_file3.mp3"); // resource with ID 3
// IDs can be arbitrary but they should be unique across all translation units in your project
...

This header will be used to access the resources from your application code. If you include it and compile your project now, you will get a linker error because the resources were just declared.

To actually embed all the files, run rescomp on the same header file:

$ rescomp resource_list.h -o resource_bundle.o [-R resource_search_path] [-I resman_include_path]

This will generate a static library or an object file containing the resource contents (as if compiled from the textual byte array). Clang parser is used to extract resource IDs and file names (and to do some error checking), LLVM is responsible for emitting the correct native object format.

Now, if you want to use a resource, construct a ResourceHandle from the global Resource<ID> variable:

#include "resource_list.h"
...
resman::ResourceHandle handle{gRes1};

// ResourceHandle provides convenient interface to do things like:

// iterate over bytes
for (char c : handle) { ... }

// convert bytes to string
std::string str{handle.begin(), handle.end()};

// query size and id
unsigned size = handle.size();
unsigned id = handle.id();

Make sure the linker can find rescomp's output and your project should build now.

So, to summarise: Instead of generating byte arrays, you just write a header file with the list of resources. Then you run the resource compiler to generate object files or static libraries directly from that list. That same list will also be be used to access your embedded resources.

Configuration

The rescomp interface is quite simple. It just takes one or more header files and produces one object file or static library. All resources declared in these headers are packed into the output file.

Required parameters

<input_file> [<input_file> ...]       Input header file(s)
-o <output_file>                      Output format is determined from the file extension you provide

Optional parameters

-I <directory> [-I <directory> ...]   Include search path
-R <directory> [-R <directory> ...]   Resource search path

Some directories are always added into search path implicitly:

  • Current working directory (resources and includes)
  • Program directory (includes only)

The inclusion of program directory is just for convenience; i.e. if you have resman.h saved next to rescomp, includes like <resman.h> or "resman.h" will be resolved without any additional -I parameters.

Build system integration

For an example project that uses CMake, see the examples directory.

Generally, any build system which supports custom targets can be used.

Installation

Prebuilt executables for Windows/Linux/macOS are available in the release section. Just unpack anywhere and enjoy!

The Linux binaries are linked statically so that they can run on any distribution.

I used Void Linux with musl libc which supports true static linking unlike glibc.

Download latest release

Building from source

Dependencies

  • LLVM/Clang 6

For Linux, there is a docker image prepared which has all the necessary dependencies installed. You can see how to invoke the build in build_scripts/linux/travis_script.sh.

On macOS, you can download LLVM/Clang from the official website and then invoke the build (see build_scripts/osx/).

If you're building on Windows, there is a Visual Studio solution. LLVM/Clang prebuilt libraries are not available officially though. I use my own build to speed up AppVeyor jobs.

How it works

TODO