(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, 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, 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. (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...)
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
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.
- Is this use of
package.links(enabling link overrides but not defining symbols) appropriate?
- Is the default behavior of just emitting
cargo:link-liband hoping for the best reasonable?
- Is deciding which binary to link based on
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...) ↩︎