(CYA disclaimer: I am not a lawyer and nothing in this post constitutes legal advice. TL;DR at the bottom.)
My bindings to the FMOD Engine are nearing a usefully usable state. Unfortunately, due to FMOD being proprietary licensed software, there are some complications involved in packaging the bindings for distribution via crates-io. Specifically, while the EULA permits use of the SDK (that being, roughly, the headers and documentation) for this purpose[1], the actual engine binaries MUST NOT be redistributed as part of a game engine or tool set.
So, okay, I can publish my bindings for working with the FMOD SDK, but must leave acquiring of the FMOD Engine to be done by the downstream user of my bindings. How should this be done to best integrate into Cargo's build system, and in a way that's at least somewhat portable?
My current solution is that the sys crate sets the package.links
manifest key, despite this being questionably a misuse[2], specifically such that downstream can use a link override to link the library if the behavior I provide isn't sufficient. (Although, there is a license available which grants source access and static linking, and I might eventually add support for that mode to FMOD.rs if I purchase that license for other reasons, in which case the links key becomes a lot more appropriate.)
Then, by default, the buildscript just emits the cargo:rustc-link-lib=LIB
directive, expecting the library to be available in the default search path. (If it's not, use the link override.) I also offer an optional link-search
Cargo feature which attempts to determine what the conventional install location for the SDK on the host would be in order to emit a cargo:rustc-link-search=PATH
directive, but notably I'm as of current choosing to panic the build if link-search
is set but I can't guess a conventional install path[3]. (I currently allow the build to continue if I can guess a conventional path even if the path doesn't exist on disk; I wonder if that should also be a fail-the-build condition, notifying the developer that they need to acquire the engine separately instead of getting an ugly link error...)
Finally, since feature = "link-search"
is searching outside of OUT_DIR
, it matters for build-time linking, but doesn't impact the dynamic library search path, meaning that even with feature = "link-search"
the developer still needs to copy the runtime dylib into the directory where they're doing cargo run
to allow the built binary to actually load the dylib and work.
Finally, FMOD ships two versions of the library: fmod.dll
(release binary for production code) and fmodL.dll
(release binary with logging enabled for development). Which one gets asked for is determined by the PROFILE
environment variable, which reports whether the used profile inherits from dev
or release
(roughly whether --release
is being used). No way to change this other than link overrides is provided.
I'm agonizing over immediately getting this "right" for the sys crate perhaps a bit more than I should because in order to usefully match the sys crate version to the library version, they're starting out at a semver-stable 2.YY.ZZ
version, making fixing mistakes harder. Whatever interface I'm committing to for the buildscript should stay backwards compatible for approximately forever.
Concrete questions:
- Is this use of
package.links
(enabling link overrides but not defining symbols) appropriate? - Is the default behavior of just emitting
cargo:link-lib
and hoping for the best reasonable? - Is deciding which binary to link based on
PROFILE
reasonable? - Should
feature = "link-search"
...- Panic the build if there isn't a known conventional SDK path?
- Panic the build if the conventional SDK path is known but doesn't exist and contain the binary?
- Copy the searched out binary into the output directory somehow?
Am I overly stressing about this?
I believe the EULA permits this use, anyway, but IANAL and this does not constitute legal advice. I have reached out to Firelight Technologies to ask for clarification before publishing. ↩︎
Specifically, a primary benefit of the links key is to ensure only one crate links a native library and provides its symbols, as multiple crates providing a native lib could lead to duplicated symbols. Since linking to the dynamic library doesn't define any symbols, and it's perfectly fine to link the same library multiple times, I don't necessarily need this first behavior, and am using the links key primarily for the ability for downstream to override the link handling. (There is global state that needs a global lock to be exposed safely, but for better or worse, that lives in the wrapper crate without a links key. Maybe I should pull the global lock into a separate crate shared between breaking revisions to the wrapper so duplication isn't a soundness hole...) ↩︎
There certainly exists a conventional directory for Windows (the download comes as an installer), and there might be on macOS (I need to pull out my Mac to check how the .dmg behaves there), but there certainly isn't for Linux (the download is just a .tar.gz). I have no idea how the pkg-config/vcpkg/etc tools are supposed to work, but this very much isn't a "system library" anyway, it's a dylib (Windows .lib and .dll; macOS .dylib; Linux .so) that you're expected to ship with your application. (I wonder if I can/should be using raw-dylib on Windows now rather than the .lib import library...) ↩︎