Description
It would greatly benefit library developers if libraries could specify additions to the include path used in their compilation. Developers could then create work that is much easier to maintain.
Problem
In my personal use case I would like to write both a portable C driver for a device (that can be used on other platforms and with minimal overhead) and an Arduino wrapper for that interface that makes it simple to use for beginners. For the purpose of illustration it would be best to think of the portable driver as immutable source code obtained from a third-party. For maintainability the third-party code would be included 'symbolically' (e.g. git submodule). A representative library structure might be:
libraries/Servo/library.properties
libraries/Servo/keywords.txt
libraries/Servo/src
libraries/Servo/src/Servo.h # this is the Arduino wrapper interface
libraries/Servo/src/Servo.c
libraries/Servo/src/open-source-servo-driver-lib/include/ossdl.h # this is the portable open-source interface
libraries/Servo/src/open-source-servo-driver-lib/src/ossdl.c
The file ossdl.c
will most likely include the open-source portable interface with a line such as: #include "ossdl.h"
-- unfortunately this will not work in Arduino 1.8.10
The workaround(s) is(are) not great:
- Maintain a Copy of OSSDL: Increases mainenance workload, detracts from open-source spirit (collaborating on original source rather than forking and never coming back)
- Use Relative Paths in OSSDL.c:
#include "open-source-servo-driver-lib/include/ossdl.h"
inexplicably associates the OSSDL project with Arduino build requirements - Roll OSSDL into Arduino Library (If you are the maintainer of OSSDL) Same issues as maintaining a new copy, and if you abandon the standalone copy then other potential users have to work around additional code (the Arduino implementation)
Proposed Solution
I suggest that library.properties gains a new comma-separated field of extensions to the library's include path (relative to Servo/src
). If not present (as in the case of existing libraries) only Servo/src
will appear in the search path - keeping compatibility with the existing codebase. For the sake of interface protection this is as good as the current technique because users would have to modify library.properties
to get access beyond the developer's original intent. Of course if they are modifying the library they don't need this feature to accomplish what they seek.
To fix the example situation from above the developer would add the following entry to his library.properties
file:
include=open-source-servo-driver-lib/include
Of course it would also be valid to use
include=lib1/include,lib2/include,lib2/src
since commas are not (typically) used in filepaths. If needed another format (or use of escape characters) could be employed.
Other Possible Solutions (thoughts?)
- Linking Precompiled Libraries: Although this may work in some cases it is counter-productive to educational (and open-source) efforts not to mention that users will lose control of compilation options
- Recursive Search for Library Headers: This is the more extreme version of what is proposed - all
.h
files underneath thesrc/
directory would be accessible. Concerns of interface preservation/enforcement would be raised.
Related Issues / Feature Requests
arduino/arduino-builder/issues/15 - A long-debated feature in which it is requested to put the Sketch folder on the include path for libraries. This is not the same request because
- It keeps the library developer in control of the API
- Does not add complexity for the typical user
Activity
per1234 commentedon Nov 22, 2019
Please explain why you consider the existing solution of using relative paths to be "not great".
oclyke commentedon Nov 22, 2019
Relative paths themselves are not the problem (surely they are used in many projects out there) but rather the fact that only the
src/
directory can be used as the reference for those relative paths.The goal is to be able to include other open-source projects in Arduino libraries without modification - thereby greatly lightening the burden of maintenance.
Lets imagine that there are two great projects that should be included in an Arduino library. Their structure is not flat and header files may be located in one or more sub-levels. Each project might have a structure similar to this:
- projN - subA - headA.h - subB - headB.h - projN.h (#include "headA.h" and #include "headB.h") - projN.cpp
AFAIK there is no way to use this project without modifying it - even if you placed the contents of
projN
under the library'ssrc/
directory the Arduino build system could not findheadA.h
orheadB.h
. The source code of the (third-party) project would need to be modified to include headers via relative paths.Maybe it is best practice for a project like
projN
to use relative paths internally so that only one include path is needed in the build system but unfortunately we can't enforce that on third-party projects. So the alternative is to maintain a modified fork/copy or to allow additional include paths to be added to the build system.Is there another way that I am overlooking?
facchinm commentedon Nov 29, 2019
Hi @oclyke ,
I indeed like the idea of allowing a library to specify additional include paths; it should also be totally safe (in fact, we are only recursively compiling inside the
src
folder, not outside).My proposal:
include=
because it's too similar toincludes=
(https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5:-Library-specification#libraryproperties-file-format) ; maybeadditional_include_paths=
is a bit better?I'll try to prototype something and let you know 😉
oclyke commentedon Nov 29, 2019
Wow fantastic! Can't wait to test drive. I've never worked on the IDE source code but let me know if there's anything I can do to help.
Add support for additional_include_paths library property
facchinm commentedon Mar 10, 2020
@oclyke sorry for the delay. I'm attaching the builds of
arduino-builder
for the most common platforms to #502Add support for additional_include_paths library property
matthijskooijman commentedon Mar 11, 2020
What does this mean exactly? The environment for include detection and compilation should be as identical as possible, otherwise you might end up with different results and missing includes during include detection?
Do you maybe mean that these additional include paths should only be used when compiling source files from the library itself? If so, the include path should be added only for this library's source files, when compiling and when doing include detection I think. However, I'm not so sure this would really work: If the sketch includes a library header file that wants to include a header file from the additional include path, this would break. Because of this, in practice you pretty much always need a single, global, list of include paths used in all compilations.
@oclyke Are you sure this will not work in Arduino? When you include with
""
rather than<>
, the compiler should resolve the include relative to the current file, so you should be able to include from the current directory.This does not solve the more complex problem when you have two immutable sublibraries of which one needs to include the other, so the additional include paths can still be useful.
On a related note: Maybe it would be interesting to also have an option to not put the
src
directory in the include path at all. This would allow libraries with a separateinclude
directory for their public interface, and still allow "private" headers in theirsrc/
directory that can be included with relative includes (with""
).facchinm commentedon Mar 11, 2020
With
avoid polluting the search space
I was thinking about this usecase (let's call it "Arduino style explicit library request" )Then,
foo.h
is discovered inFoo
library with the following structureFoo library adds
libbar/include
toadditional_include_paths
and this stops the library discovery there. Since the user wanted to addbar.h
fromBar
library (in sketchbook) the compilation is now broken.Now, there are a couple of real life things to keep in mind:
Hope it's clearer now, but I'd love this thing to be ready soon and having a larger audience can help in finding the ideal solution.
matthijskooijman commentedon Mar 11, 2020
@facchinm I'm a bit confused with your example. The sketch includes
"bar.h"
, the quotes indicate that this file exists in the sketch? If so, there is no conflict there, as the quotes make sure that the current directory is searched first, so the file from the sketch is found. If they wanted to includebar.h
from theBar
library, it would have been better to write#include <bar.h>
instead. In any case, if the sketch contains nobar.h
, then either one has the same result.Furthermore, you seem to suggest a conflict between
bar.h
in theBar
library and the include inlibbar
, but in your example you only have alibbar.h
, so again, no conflict there?Assuming that you intended no
bar.h
in the sketch, and intendedlibbar.h
to be namedbar.h
, then indeed, there is a conflict betweenlibbar/include/bar.h
andBar/bar.h
, but I don't think there is a way to solve this.Your suggestion seems to be to only put
libbar/include
on the include path when compilingfoo.cpp
andlibbar/src/bar.cpp
? If so, this would break if the sketch includesfoo.h
, and that does#include <bar.h>
(intending to includelibbar/include/bar.h
). This could of course be solved by doing a relative include (#include "libbar/include/bar.h"
) instead (which is a good idea in any case, since it disambiguates), but does not solve everything. Consider the following library:Now, suppose that libbar depends on libbaz and
libbar/include/bar.h
contains#include <baz.h>
. If the sketch is compiled without additional library paths, and it includesfoo.h
which includeslibbar/include/bar.h
which tries to include<baz.h>
, which is not in the include path and fails. Sincelibbar/include/bar.h
is immutable, we cannot fix this using a relative include.I guess this means the perfect solution does not exist (the problem, really, is that "includes" in C/C++ inhabit a global namespace and can cause conflicts). The traditional way to solve this is to do have a single (or a few) include dir instead of one per library and doing things like
#include <libbar/bar.h>
where thelibbar
part provides some namespacing, but that seems infeasible with the way Arduino works right now (in fact, it is actively prevented, since library autodetection does not work on files in subdirectories, so you cannot even emulate this behaviour with a subdirectory insidesrc
currently).Given there will be some limitations either way, I think I would prefer to keep the include namespace simple and flat, and let people deal with duplicate header files (which then need to do already, if multiple libraries provide a
bar.h
file, they might run into trouble), rather than letting them deal with inconsistent results for inclusion ("it works when I include file from my library .c file but not from the .h file, huh?") and not supporting multiple interdependent sublibraries.17 remaining items