I'm writing C code for an embedded system (dsPIC33 platform), and I'm considering building up a reusable code library to use among multiple projects.
What are the best practices for tying the library to each project?
Obviously the library will have some hardware-specific (and thus project-specific) dependencies, so it's reasonable to assume that it will be compiled with each project (instead of linked in binary form).
What I've come up with so far is to keep the library centrally located, but require a project-specific libraryConfig.h that includes function definitions, macros, etc. This requires that the library include the header in its own code, which means that the project source directory will need to be in the include path (not just the library source directory). That kind of messes up the distinction between #include ""
and #include <>
, doesn't it?
Is this how it's done normally?
A very good question and the answer isn't simple. Several things to consider. Here are a few opinions from my experience so far.
Common Code vs Project-Local Copy
One important decision is whether to use "common" library code that is updated automatically from a central location (your company's "reuse library"), or whether to keep a project-local copy.
This is discussed in detail in this SO question.
The benefit of a central library is that work done once can benefit many projects. The difficulty with a project-local copy is that any bug fixes and improvements aren't contributed back to the central library, and any bug fixes in the central library may not be brought into your project.
But a potential difficulty with using a central library is if people on their particular modify it in an uncontrolled way to suit their project, and it unintentionally breaks other projects. I've seen that personally, in "common" code that became full of #ifdefs and regularly broke other projects.
To get good value out of common code aka central reuse library:
The library:
Individual projects:
If a company doesn't have such a process in place, then a project should just make a local copy of a piece of code (say, copied from a previous project) and then take full project-local responsibility from then on. You're still getting some benefit from reuse in that situation, because you're not rewriting it from scratch.
Project-Specific Configuration
If the code needs project-specific configuration, ideally that should be kept to as small a part of the code as possible--not scattered through a bunch of source files. Ideally, a single header file. But quite possibly a .C file as well (say, if you need to define some look-up tables). The library should provide a template, with the options well-commented.
For a good example of how this can be done, see the µC/OS-II RTOS (book) by Jean Labrosse, from Micrium.
It doesn't mess up the distinction, which is almost entirely platform-defined anyway. The only defined behaviour is that if an include using ""
fails to find the file, then it searches again as if you'd said <>
.
I think you're doing the right thing. The normal way to handle a platform-specific header, in my experience, is that you give it a name you're as confident as possible will never collide with anything else, and #include it with ""
. Then you tell the platform porter to do whatever compiler-specific thing is necessary to ensure that it is found. Normally that just means specifying some compiler argument like -I, for wherever he wants to keep the file. So yes, one of his project's directories. But if all else fails he can always copy his file into some place where his compiler will look. He could even copy it into his local copy of your library source, if his compiler is being unreasonably difficult about the whole thing.
Another way is to have a file in the library, selectplatform.h, looking like this:
// obviously WIN32 isn't an embedded platform, and GCC is too broad
// to be supported by a single header file. Replace with whatever platforms
// it is you do support out of the box.
#if _WIN32
#include "platforms/msvc32.h"
#elif __GNUC__
#include "platforms/gcc.h"
#else
#error "You must add a new clause to selectplatform.h for your platform"
#endif
This avoids the need for compiler configuration, but has the downside that every new platform port has to modify the file. If you're the only one doing any porting that's definitely not a problem. Otherwise that one file is forked by third parties. Then maybe they add a new file to platforms/
in your library, or maybe they put their file elsewhere. So with third parties, it's only probably not a problem. They can contribute their changes (possibly including their platform's header) back upstream if they and you both want.
No.
Normally you define a path to your lib's includes directory using a command flag in your compiler (usually, it is -I flag).
Say, if you are using GCC compiler, and your library's header files are in the
/usr/local/include/mylibheaders
then you must call compiler with following option:
-I/usr/local/include/mylibheader/mycurrentplatform
where mycurrentplatform directory is different for each project and contains project-specific libraryConfig.h
Thus, you can use #include<libraryConfig.h>
in every project.
This is really more of a configuration management question than a C question. In my experience, using a good version control program is most helpful. Find one that allows you to define a "project" by pulling source code from several different locations. Realize that your version control program's definition of a "project" will then become an essential element in building the project.
It is also important to be able to make changes to your library code for a project branch and check them into your version control system several times without having to check in the changes to the main library location until the changes are proven since they may affect many different projects.
Your library modules may also end up with a file that defines library options for each specific project. A practice I have adopted is naming these interface files _PAL.h where the _PAL indicates a Project Abstraction Layer file.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With