Crate organization and metadata for bindings to an LGPL library

When updating libftdi1-sys, a binding to libFTDI that I've long neglected, I bumped into an interesting problem that boils down to feature-dependent licensing.

First, a bit of background. libFTDI is an open library for working with FTDI chips that are USB-to-something converters, where "something" can be set to various configurations ranging from plain old serial port to high-speed FIFOs operating close to the USB 2.0 bandwidth limit. Unlike the proprietary vendor drivers, libFTDI core components are licensed under GNU LGPL 2.1. There are also non-core components like the C++ bindings that are under GPL, but these are out of scope for this binding.

So I want to have a binding that would be both well-behaved and easy to use. Roughly I'd like it to have the following set of features:

  • uses system libraries by default, finding them with pkg-config or vcpkg (no idea about Macs, sorry) and generating bindings with bindgen on the fly
  • has an option to use pregenerated bindings to avoid the bindgen and clang dependency
  • has an option to use a vendored copy of the library for the cases where the system one is not available
  • is under MIT OR Apache-2.0 to match the community preferred licensing and avoid LGPL...
  • unless the vendored copy is used because that definitely makes the executable a derivative of LGPL-licensed code.

IANAL, but from reading LGPL I think that none of these should be a problem on their own. However, the cargo-native way of indicating the package license, which is the package.license field in Cargo.toml, cannot depend on any crate features. So, what can I do?

Option 1. Just make the crate LGPL. Advantages: trivial. Drawbacks: for Rust's static linking this is basically equivalent to GPL, so really restrictive.

Option 2. Use package.license-file instead and write that in prose. Advantages: no real changes needed. Drawbacks: not machine-readable, so does not work with tooling like cargo-lichking.

Option 2a: Just write that all in the README and leave one of the licenses for all cases. Advantages: stupid simple. Drawbacks: the machine-readable information is incorrect and actively misleading the user into either rejecting the crate without real need to, or, worse, accepting the crate without noticing the problem.

Option 3. Make a separate crate libftdi1-vendored-lgpl-internal under LGPL, move the actual vendored source to it and link to it only with the vendored feature (but still generate all bindings in the main crate). Advantages: correct and machine-readable. Drawbacks: non-obvious for possible contributors (especially the "the library and bindings to it are compiled in different crates" part).

Option 4. Make a separate crate libftdi1-sys-vendored under LGPL that both contains code and generates bindings for it; with the vendored feature libftdi1-sys just reexports the bindings from it. Advantages: correct and machine-readable. Drawbacks: the binding generation logic is duplicated between the two crates.

Option 5: Create a generic marker-includes-LGPL-code crate and depend on it with the vendored feature without splitting anything. Advantages: almost correct (the license is not on the correct crate, but it is in the dependency tree) and machine-readable. Disadvantages: may be not exactly obvious for users, may be "hacked around" (well, I hope no one would replace that crate with a permissively-licensed version just to silence the tooling, but still).

For me options 1-2 look like non-options. Anything else? Am I missing some standard and accepted solution? Which option would you prefer?

Options 3 and 4 sound fine to me.

If the crate is designed to be used as dynamic library, it's supposed to have a stable API, which means you don't need to generate bindings dynamically. Generate them offline once per ABI version, and include in the sys crate.

1 Like

That's how it was implemented in the initial version, but a new contributor pointed me at the bindgen guide that recommends generating the bindings on the fly and it sounds like that's a good idea for the case someone wants to use the crate on a target with not-yet-supported ABI, no? Also, I'm not sure how one's expected to generate bindings for ABIs used on hardware one does not have access to.

I disagree with bindgen's recommendation here.

If the library doesn't do anything weird (such as explicit #ifdef that changes things between platforms), then bindgen-generated bindings will be universal and work on all platforms, current and future. This is because the ABI will vary according to C ABI, and #[repr(C)] does the same thing too.

Only thing that breaks in bindgen is layout tests, which hardcode platform-specific details in an inflexible way. Simply skip them, and it will be fine.

Ok, thank you for input. I'm leaning towards option 3 for the LGPL issue and towards leaving compile-time bindgen as an off-by-default option for weird cases if there are any.

As a brief update: looks like option 3 from the initial post (keeping the C source and bindings in different crates) does not work (or at least is hard to make work). Naively implementing it causes link errors, and so far I haven't succeeded in tricking cargo/rustc to link everything including the C code. I'll probably fall back to option 4 (generate bindings in the vendoring crate and reexport in the standard one with the vendored feature).

So, in the end (well, maybe not the end, but the current PR) I went with yet another strategy.

Option 3a. Make a separate crate libftdi1-source-lgpl under LGPL, move the actual vendored source to it and use it only with the vendored feature. The crate just copies the source into the build script OUT_DIR and provides that path to the main crate. Compilation, linking and binding generation are all done in the main crate.
In theory the crate could be a build dependency, but instead of that it is intentionally made a regular dependency to indicate that LGPL applies to the compiled artifacts.
Advantages: correct, machine-readable, mostly obvious structure.
Drawbacks: the main crate contains code used to build the source even though it can be used without the source (in this case the code is almost trivial, but that may be not always the case); the Rust source of the lgpl crate is not really used, which might cause false negatives if the license checker is "too smart" and, for example, checks whether the final artifact contains code originating from that crate (but that also would cause problems in cases of code generation).

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.