0

I understand that with many processes using a common shared library, the binary code in the shared library can be updated without necessarily having to update all those processes. It's fine as long as it is an internal implementation change, and does not remove exported functions, or change the arguments of exported functions.

I'm not sure how to reconcile this though with Makefile dependencies. The simple guideline is if the file is referenced by any of the commands, then it is a dependency and should be listed as a prerequisite in the Makefile rule.

When linking an executable, it definitely can reference a shared object, and so depend on that shared object. However, it doesn't seem like a relink is always necessary just because the shared object changed.

So I'm wondering about listing library as dependency:

all: process

# listing library.so as dependency
process: file.o library.so
    gcc -o process file.o library.so

library.so: library.o
    gcc -shared -o library.so library.o

library.o: library.h

vs. not listing it:

all: library.so process

# not listing library.so as dependency
process: file.o
    gcc -o process file.o library.so

library.so: library.o
    gcc -shared -o library.so library.o

file.o: library.h

library.o: library.h

Suppose library.h contains declarations for all exported functions from library.so.

If library.so is a dependency for process, then it seems if I only change the implementation of a function (no change to exported function signatures), then I don't need to relink process, but a make command would relink library.so, and then in turn relink process.

However, if library.so is not a dependency for process, then make would still relink library.so for an implementation change via the all target, but not relink process. If I change the data type of a function argument, add/remove a function argument, or add/remove a function, then library.h will have a change. This will in turn trigger recompiling file.o, and in turn trigger relinking process.

Use of library.h to control recompile of file.o, and so indirectly control relink of process is still imprecise though, because file.o might not actually reference any of the exported functions from library.so that changed.

I reviewed this question, but I couldn't find anything specific about shared object dependencies for a process: Makefile Dependencies, What Should Be a Dependency?

Rich Jahn
  • 397
  • 3
  • 14
  • Generally, you include the library in your linker configuration by including the library in your `LIBS` variable. In your case with `library.so` that would generally be `LIBS := -lrary` (where as with all libraries the `"lib"` is replaced with `'l'`. Then when you compile, you use, e.g. `$(CCLD) -o $(APPNAME) $(OBJECTS) $(CFLAGS) $(LDFLAGS) $(LIBS)` where `$(LIBS)` brings in your shared library requirements. Your `library.so` will need to be in your library search path, or will need an appropriate `rpath` set and the library search path set with `-L/path/to/search` included in `LDFLAGS`. – David C. Rankin Jan 30 '21 at 05:26
  • I prefer to list the libs as dependencies if I am building them myself in a hierarchy of process/execs and libs - so that I can always just hit `make` at the top level and everything builds. But as you say, if you edit a c file in the lib, the so gets updated and the process/exe re-links. This is my preference, but might be subjective. – code_fodder Jan 30 '21 at 15:01
  • 1
    The problem is that there's no way for make to understand whether `library.so` changed in an internal way that doesn't require a re-link, or in an externally visible way. Heck, many programmers don't always get this right. And if you get it wrong the results can be extremely bizarre and very hard to debug. So, you should list the shared library as a prerequisite in your makefile and just live with the re-link (which should be quick anyway). – MadScientist Jan 30 '21 at 15:09
  • 2
    In this case I would make`library.h` a **normal** prerequisite of `file.o`, and `library.so` a **order-only** prerequisite of `process`. This way `library.so` is rebuilt if missing when linking `process`, but a newer `library.so` will not force `process` to be rebuilt. – ssbssa Jan 30 '21 at 18:17
  • What @ssbssa said; if you overthink make, your returns decrease rapidly. In *theory* your dependency on your .so should only be via the interfaces in library.h and you only need to recompile your app if an interface changes. Thus, library.h is sufficient. Yes if you add something to library.h, you will trigger an unnecessary build; but to make it truly precise requires much more intrusive build tools. – mevets Feb 01 '21 at 15:57

1 Answers1

1

If you don't list library.so as a dependency of process, if you modify one of its sources (the source files of library.so), you'll end, most probably, with an incompatible library.so with the program process and your program will probably crash when you try to execute it.

Think you change the library, eliminating a function f() from it, because you don't need it more... but your program process still calls it in its main() function. As you have not modified process.o (you didn't touch any source of it) it is not to be compiled, and no new link command will be done (because process depends only on process.o).

Last time you linked process the linker put a call to f() in main and resolved to put it at address x (corresponding to the address that existed in library.so). But as you have not stated that process needs to be linked if library.so changes, it will not be linked... and when you execute it, it will call to f() at address x, and crash.

Try this, and you'll see that if you leave the dependency on library.so, then your program will be linked against the shared library and will complaint about f() not existent. So you should modify process.c and repeat.

Always think this... Is the file library.so needed to link process? If it is, then you should depend on it. And it is, because the functions you wrote in library.so are called from process. You will see that the dependencies to .h files are put on .o targets, and never on .c files. This is because the .c and all the included .h files are needed to build the .o object. But the .c files don't depend on the .h files... what depends is the result of the compilation (and this is the .o file).

The second file, has a fake dependency on target all. That's what is called a .PHONY: target (a target that is always executed, and doesn't require a file to be created) With it you try to create both, library.so and process. If library.so is present when you link process, then it will be included (but without knowing if it is a recent buid or an old one) but if it's not, when you try to build process, if library.so doesn't yet exist, then you'll get a strong complaint from the linker and your build will be aborted.

So, as a conclussion, the second Makefile is incorrect, and you should avoid it. State the dependencies as make(1) checks them.

A final note, depending on the implementation of Make, the order of the dependencies in the right part of a rule can be significant or not. Putting library.so to the left of process can be a bad idea if you run make on a multiprocessor and try to do a parallel build (see option -j of make(1)). In a parallel build, make(1) doesn't start the build of a dependent file until all its dependencies are satisfied... but it tries to start as many processes as possible to handle that possibility on multiprocessor machines. And the build process speeds up a lot in case you have a full set of cores, but can be dangerous if you don't observe the dependencies precisely.

Rich Jahn
  • 397
  • 3
  • 14
Luis Colorado
  • 10,974
  • 1
  • 16
  • 31
  • There are many changes you can make to a DLL on Windows or SO on Linux and not have to relink an executable that uses it. The most basic thing is fixing a bug in the implementation of a function, but not adding/removing exported functions, or changing the signature of exported functions. The file modified timestamp alone of a DLL/SO is not enough information to know whether to relink it. If you explicitly link at runtime with LoadLibrary()/dlopen(), then the DLL/SO won't be in the Makefile at all, and then it won't be "incorrect". – Rich Jahn Mar 04 '21 at 18:12
  • What would work is a file for an executable with a list of functions "imported" (although you don't import on Linux), organized by library, and another file for the library with a list of functions exported. When relinking a library, the linker would have to first _read_ the file of exports if it exists, and then conditionally update the file, basically if exports are added/removed. The linker would also have to look at every file of imports, for every exe, and see if any removed functions were in use. This wouldn't really work with file modified timestamps either. – Rich Jahn Mar 04 '21 at 18:33
  • That's what you do when you use system libraries. You are clever!!! When something is not project related, you normally don't put dependencies on it in your makefile, because you put in your makefile things that it will build as part of the process. I don't understand what are you talking about. Make is just a program to build things, be those things shared libraries, executables or just documentation. What does a _list of functions "imported"_ has to do with that? Please, explain. – Luis Colorado Mar 06 '21 at 22:35
  • Make was invented well before shared libraries were invented.... so what is applicable to normal executables, is also applicable to shared libraries, or, as I've said above, to documents to be formatted, yacc processed, lex processed, etc. What you have to understand crystaline, is what is a target, and the list of files necessary to build that target. Then you have a rule.... and so on. – Luis Colorado Mar 06 '21 at 22:39
  • Make has no means of knowing what part of the shared library you have touched, if the interface or the internal implementation. So it is better to enumerate the dependency and build, than run the risk and not build, and crash your program without knowing that should have linked again your executable with the new library version, you had have a nice execution. If you don't want to make your executable dependant on the library, go ahead.... but your makefile is wrong. – Luis Colorado Mar 06 '21 at 22:43