Home > Back-end >  How to ensure that static library and application are compiled the same?
How to ensure that static library and application are compiled the same?

Time:09-18

I am working on integrating a 3rd party project into my application (LittleFS to be precise). I am attempting to compile the project as a static library then link this into my application. I have no problem with linking and getting the include paths correct, my problem is in how to handle the compile-time configuration.

For example, the project has a line like the following in its header:

struct lfs_config {
    ...
    #ifdef LFS_THREADSAFE
    int (*lock)(const struct lfs_config *c);
    int (*unlock)(const struct lfs_config *c);
    #endif
    ...
};

I would compile the 3rd party project/static library with the following flag to enable this feature:

gcc ... -DLFS_THREADSAFE

However, when I link this library into my application, for this to work, I need to also make sure that my application has the -DLFS_THREADSAFE flag set.

This means that I have to keep track of which defines are set in two separate places which seems like it will get a bit error-prone and cumbersome after a while.

My question is, how can I ensure that static library and application share the same configuration?

CodePudding user response:

There is no definitive and portable way to do this. In fact, most coding standards I've worked with will not permit any structure that is defined differently based on macros, particularly if that structure is used as part of an API. This would be considered an API design flaw if an interface structure is not consistently defined.

Rather than having the structure contents actually change based on the compile time configuration, it is better to keep it consistent but rather simply set the values to something to indicate they are not used. That is, in your example, keep the function pointers unconditionally, without any "ifdef":

struct lfs_config {
    ...
    int (*lock)(const struct lfs_config *c);
    int (*unlock)(const struct lfs_config *c);
    ...
};

In the event that LFS_THREADSAFE is undefined, these pointers can be set NULL. Assuming you use c99 style initializers, this can just work, i.e.

const struct lfs_config MY_CONFIG = {
    .other_option_1 = <value>
#ifdef LFS_THREADSAFE
    .lock = my_lock
    .unlock = my_unlock
#endif
    .other_option_2 = <value>
};

In this case, if LFS_THREADSAFE is defined, the lock and unlock items will be set accordingly. But if LFS_THREADSAFE is undefined, these initializers will be omitted, and per the C99 standard, the unspecified members will be assigned value 0 (NULL, in the case of a pointer). Inside the library, it can be made to simply skip the lock/unlock call if the function pointer value is NULL.

Alternatively, one could also always set the pointer to a valid function, but have it be a no-op if the LFS_THREADSAFE is undefined. That is:

int my_lock(struct lfs_config *c)
{
#ifdef LFS_THREADSAFE
    <do_something>
#endif
    return 0;
}

And similar for unlock.

To summarize, its always wise to keep API structures as consistent as possible, and not make them dependent on macros. Hope this helps!!

CodePudding user response:

I've reviewed the littlefs source code.

LFS_THREADSAFE is the only one (i.e. you don't need to set LFS_MIGRATE).

Just remove the #ifdef/#endif in lfs.h for it and you're done.

But, to answer your question in general: just build your project and littlefs via a "top level" makefile:

MYPROJ = myproj
LITTLEFS = littlefs

CFLAGS  = -DLFS_THREADSAFE
CFLAGS  = -DLFS_NAME_MAX=367
CFLAGS := $(addprefix CFLAGS =,$(CFLAGS))

all: lfs_mk myp_mk

myp_mk: lfs_mk
    $(MAKE) -C $(MYPROJ) $(CFLAGS) $(MAKECMDGOALS)

lfs_mk:
    $(MAKE) -C $(LITTLEFS) $(CFLAGS) $(MAKECMDGOALS)
  • Related