esp-rs / esp-idf-sys

Bindings for ESP-IDF (Espressif's IoT Development Framework)
Apache License 2.0
253 stars 117 forks source link

Co-existence of old and legacy driver wrappers in downstream crates #313

Open ivmarkov opened 3 weeks ago

ivmarkov commented 3 weeks ago

In light of this issue - and if it does not get resolved soon - the scheme below is the only one I could come up with so as to workaround the issue with a minimal effort and a minimal cognitive load to the end user:

Necessary changes to the esp-idf-sys build system

Necessary changes to esp-idf-hal

To comply with the "whole object file" style of linking described in the ESP IDF issue we have to keep the following invariant:

Code in third party crates outside of esp-idf-sys should follow the same invariant so as to deal with the issue.

How would the above ^^^ solve the problem

Alternative: doing it all with features

Rather than changing the esp-idf-sys build system from above, we can alternatively introduce a new cargo feature - in esp-idf-hal - for each new driver that pops-up in ESP IDF and keep this list of new features growing ad-infinitum (or until the legacy driver is retired in - say - ESP IDF 6 or later). To elaborate:

The above features would not be enabled by default.

Pros:

Cons:

Cons of both approaches

For backwards compatibility, both approaches will keep the legacy drivers "enabled", and the new drivers disabled. Which in the long term is not ideal, as we expect folks to start using the new drivers.

But the current reality is, we have just one new driver - ADC oneshot, with possible another in the works (I2C). Everything else is legacy drivers.

Vollbrecht commented 3 weeks ago

Thanks for summarizing all the points!

While not the root cause, i still would like to the gating at the bindgeh.h level. One example to do it with features would be to create different versions of bindgen.h and use them depending on what driver features are set on a esp-idf-sys level. Because if we do not create bindgen for old and new driver, its impossible to misuse them in a downstream wrapper since you ever get the new or the old one.

I know that this would not solve the issue with external esp_idf_components that might relight on legacy drivers etc. I only think that could be an extra measure around the things you are proposing.

ivmarkov commented 3 weeks ago

Thanks for summarizing all the points!

While not the root cause, i still would like to the gating at the bindgeh.h level. One example to do it with features would be to create different versions of bindgen.h and use them depending on what driver features are set on a esp-idf-sys level. Because if we do not create bindgen for old and new driver, its impossible to misuse them in a downstream wrapper since you ever get the new or the old one.

I know that this would not solve the issue with external esp_idf_components that might relight on legacy drivers etc. I only think that could be an extra measure around the things you are proposing.

Yes - as long as you shift the new-driver-XYZ features to the esp-idf-sys level (and then adopt and re-export on esp-idf-hal and esp-idf-svc level) that would work.

This would bind us forever with the "features" approach though.

If we take the lighter-weight approach of "if you use the sys bindings, you are on your own what you do, be careful" (which anyways applies, right), we can remove the features at some point in time.

Vollbrecht commented 3 weeks ago

Yeah that's why i thought we may could create some "user defined" kconfigs that would translate to rust cfg attributes. That way we don't need "rust features" and we would not need to split the bindgen.h into multiple files. And for example in a potential IDFv6 that has no legacy drivers we could drop them since not defined kconfigs are just ignored. E.g a IDV v6 driver could be included with cfg(any( all(adc_oneshoot, not (legacy_adc)), esp_idf_major >= 6)).

ivmarkov commented 3 weeks ago

First to elaborate about the features and what I mean that we should be probably minimalistic there:

Assume we just want to release the new crates ASAP, and not wait on the ESP IDF issue to get resolved. Then we can use the "features" approach as a minimum "band-aid" until we have clarity what is the path forward.

In one release iteration from now, we might get the default (components-based) solution (whenever we get some time to write it) and we can retire the just-introduced feature. (If we have the default solution, we can actually extend it to the bindings.h header too - it would be trivial and would make sense). Or we might even get a better solution coming from ESP IDF itself. Or become more intelligent ourselves in the meantime.

Yeah that's why i thought we may could create some "user defined" kconfigs that would translate to rust cfg attributes. That way we don't need "rust features" and we would not need to split the bindgen.h into multiple files. And for example in a potential IDFv6 that has no legacy drivers we could drop them since not defined kconfigs are just ignored. E.g a IDV v6 driver could be included with cfg(any( all(adc_oneshoot, not (legacy_adc)), esp_idf_major >= 6)).

Please elaborate it the way I elaborated the other two solutions, with pros and cons, and ideally - comparing to the other two solutions in terms of development complexity, ergonomics for the end user and so on. Then we have a meaningful basis to discuss on. Because for the moment, I'm a bit in the dark how this solution would improve on the other two.

That way we don't need "rust features" and we would not need to split the bindgen.h into multiple files.

I'm not sure from where the need to split bindgen.h is coming from?

=> Example with components and the ADC oneshot drivers:

=> Example with features and the ADC oneshot drivers:

... where ESP_IDF_SYS_FEATURE_NEW_ADC_ONESHOT is a -D bindgen parameter that the esp-idf-sys build system defines and passes to bindgen only if feature = new-adc-oneshot-driver is enabled (which is of course not to be checked with a #[cfg()] but there is a way.