ralfbiedert / interoptopus

The polyglot bindings generator for your library (C#, C, Python, …) 🐙
MIT License
321 stars 28 forks source link

support/documentation for building the library outside of tests #104

Open nihohit opened 5 months ago

nihohit commented 5 months ago

[edit:] The request is now for documentation / example for the full build process. see discussion.

[original post:] I'm looking through the documentation, and couldn't find an example on how to create bindings (specifically, C#) during the build process - the given example is for running this in tests, which IMO is a peculiar pattern, and it isn't the Rusty way to do this - this is a library, so the correct place for generating extra artifacts is in build.rs. This, with the comment saying "In real projects you might want to add this code to another crate instead" raises also the question of how/when is my library actually compiled, with added questions such as whether its compiled in release mode, etc.. Am I missing some more ergonomic way to do this?

ralfbiedert commented 5 months ago

A few thoughts:

the given example is for running this in tests, which IMO is a peculiar pattern,

I agree this is mildly abusing unit tests for brevity. That said, often in these unit tests we also want to check that currently generated bindings still match the previously generated bindings.

and it isn't the Rusty way to do this - this is a library, so the correct place for generating extra artifacts is in build.rs.

I don't quite agree for two reasons:

For one, build.rs is a pre-build script, not a build script. Given the weird things foreign (proc) macros can do, there are instances where you have more information during / after compilation of your crate, not before. If you don't want to use the unit tests hack the best thing to do is creating a dependent bin crate, which runs the interoptopus build logic in its main.rs.

Also, Interoptopus is both a library and a build tool. There is a trade-off where and when bindings are generated, and what that implies. We basically have a two-part process: 1) getting function and type info, and 2) writing bindings for a specific target language and info generated in 1).

In Interoptopus step 1) happens during compilation of the actual library, which has the advantage that when you work on and compile your library, you immediately know whether you bindings generation will work, or if you added some unsupported constructs (in all fairness, a backend might still not write bindings correctly, but that's a bug of that backend then).

On the downside, that also means we're rather tightly coupled (as a proc macro) to the Rust compiler, and running "out-of-band" is not supported.

With that said, changing how binding generation works can (and maybe should) be changed. In particular supporting 3rd-party types right now is painful, and changing the generation system would address that.

This, with the comment saying "In real projects you might want to add this code to another crate instead"

This is what I meant above. To clarify, your crate layout should be (as we're using it in our projects):

your_crate_core/               -- basic logic if your library in pure Rust
your_crate_ffi/                -- binding logic translating Rust to C-like surface API
your_crate_ffi_bindings/       -- a simple `main.rs` that depends on `your_crate_ffi_bindings` and runs Interoptopus

raises also the question of how/when is my library actually compiled, with added questions such as whether its compiled in release mode, etc..

This depends on your build config, but in essence you could just compile the entire workspace in release mode, then you'd get your_crate_ffi.dll or so, together with matching bindings.

nihohit commented 5 months ago

Thanks for the quick answer!

I think that writing this in the docs would be very helpful, as would having a sample project with the 3-tier structure that you describe. Specifically, clarifying how & what to build in release or debug mode. The reference project is awesome, but it is unclear whether it describes your_crate_core or your_crate_ffi in your schema. If I understand your description correctly, the FFI headers and inventory generation should be in your_crate_ffi - is that correct? If so, can it re-export enums or other types from your_crate_core without repeating their declaration?

ralfbiedert commented 5 months ago

Yes, sorry, the docs could be better and I'd love to have a book or so.

The reference project is awesome, but it is unclear whether it describes your_crate_core or your_crate_ffi in your schema. If I understand your description correctly, the FFI headers and inventory generation should be in your_crate_ffi - is that correct?

My preferred setup goes like so: Have a cargo workspace with at least 3 crates in it, see example above.

Specifically, clarifying how & what to build in release or debug mode

I don't quite understand, but given the example above:

Was that what you were asking?

nihohit commented 5 months ago

Yes, that answered my question.

Thank you very much :)

fakkoweb commented 2 months ago
  • your_crate_ffi_bindings is a small "application" crate that mostly depends on your_crate_ffi and Interoptopus. In its main() {} function you would just add all the Interoptopus logic you'd find in the unit tests, such as writing C# bindings.

Is this the way that is supposed to be when in the documentation is said "this in reality should be in a separate crate/application" ?

I was also confused if it meant what you specified above. Also, would it happen in its build.rs (you mentioned main) or in a test inside your_crate_ffi_bindings?