2

I am wondering if it is possible to setup a makefile rule so as to automatically remove object files which are no longer present in the library without having to do a clean build. I have my makefile setup like this.

SRC_FILES = a.c b.c c.c

libtest.a : $(SRC_FILES:.c=.o)
    ar -rcs $@ $?

%.o : %.c
    gcc -o $@ -c $<

Now suppose i remove c.c from SRC_FILES, I want the next make run to delete the corresponding object file from the archive. Is there any way to do this without having to run a clean build? Deleting the archive first and then rebuilding it doesn't work since the rule is never invoked when the library is more recent than all its dependencies. I also don't wanna rebuild the library if nothing actually changed, so making it a .PHONY wont work either.

fuz
  • 88,405
  • 25
  • 200
  • 352
user1353535
  • 651
  • 1
  • 5
  • 18
  • 2
    Rebuilding the library is extremely cheap (basically, the object files are concatenated and an index is generated). Are you sure this is inacceptable? – fuz Nov 09 '15 at 17:01
  • 1
    The problem is, the rule to rebuild the library is never invoked since the library is more recent than all its objects. Suppose I remove c.c, then the library depends on a.o and b.o, both of which are older than the library. – user1353535 Nov 09 '15 at 17:02
  • 2
    You can manually remove the library with `rm libtest.a` and then regenerate it if you want. – fuz Nov 09 '15 at 17:04
  • Duplicate of this http://stackoverflow.com/questions/6835722/relink-makefile-target-when-list-of-dependencies-changes ? – fghj Nov 09 '15 at 17:06
  • 1
    I didn't ask for 'manually' – user1353535 Nov 10 '15 at 18:48

3 Answers3

1

Although it's an old question and Etan already gave an answer. I tried to solve it like Etan but with "more make" and "less bash":

LibSources = $(wildcard *.c)
LibObjects = $(patsubst %.c,%.o,$(LibSources))

Lib := libtest.a

LibContent  = $(if $(wildcard $(Lib)),$(shell ar t $(Lib) | grep -v "^__"),)
LibRemoves  = $(filter-out $(LibObjects),$(LibContent))
SrcRemoves  = $(patsubst %.o,%.c,$(LibRemoves))
ArDelete    = $(if $(LibRemoves),ar d $(Lib) $(LibRemoves),)

.PHONY: all clean

all:    $(Lib)

clean:
        $(RM) $(Lib)

$(Lib)(%.o) : %.o
        ar cr $@ $^

$(SrcRemoves) :
        $(ArDelete)

$(Lib) : $(Lib)($(LibObjects)) $(SrcRemoves)
        ranlib $(Lib)

Note that this uses the implicit rule for creating object files.

BSD ar creates members like __.SYMDEF for the global symbol table. The -grep -v is used to filter out this member.

A bit more details

LibSources = $(wildcard *.c)

would expand in your case to a.c b.c c.c or what ever files with extensions .c you have.

LibObjects = $(patsubst %.c,%.o,$(LibSources))

gives the list of object files that should be in the library. For example. a.o, b.o, c.o.

LibContent  = $(if $(wildcard $(Lib)),$(shell ar t $(Lib) | grep -v "^__"),)

If the library already exists this gives a list of archived object files. The entry __.SYMDEF SORTED for the index needs to be filtered out.

LibRemoves  = $(filter-out $(LibObjects),$(LibContent))

That gives the difference, i.e. the list of object files to delete. And

SrcRemoves  = $(patsubst %.o,%.c,$(LibRemoves))

therefore expands to the list of source files that were removed.

ArDelete    = $(if $(LibRemoves),ar d $(Lib) $(LibRemoves),)

If nothing needs to be deleted it expands to an empty string, otherwise to the command ar d libtest.a ...objects-to-delete...

The prerequisites for the library are: all objects are members of the archive, and objects are removed if a source file was removed. In case the library was modified the index gets updated:

$(Lib) : $(Lib)($(LibObjects)) $(SrcRemoves)
        ranlib $(Lib)

GNU make supports rules for archive members. If an object file is not a member of the archive it gets added:

$(Lib)(%.o) : %.o
        ar cr $@ $^

If source files were deleted (or renamed) the corresponding object files get deleted in the archive

$(SrcRemoves) :
        $(ArDelete)
Michael Lehn
  • 2,934
  • 2
  • 17
  • 19
1

Another robust way is to make all your object files, archives, shared libraries and executables depend on Makefile that builds them. In recipes you $(filter-out Makefile,$^) to filter out Makefile from compiler, ar and linker command lines. That works best with non-recursive makefiles.

This way whenever you change your Makefile it rebuilds and relinks automatically. An ideal way would be to rebuild only if compiler options changed. It is possible but requires a bit of extra effort that may not be worth it (ninja does that automatically, but since ninja build files are normally generated by a higher level build system, like CMake, that feature is almost pointless).

Also, if you do not distribute .a files, you may like to use thin archives with T ar option to avoid needless copying of object files into the archive.

GNU ar can optionally create a thin archive, which contains a symbol index and references to the original copies of the member files of the archive. This is useful for building libraries for use within a local build tree, where the relocatable objects are expected to remain available, and copying the contents of each object would only waste time and space.

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
0

There's nothing automatic or built-in that you can do. make just isn't very good at noticing this sort of thing. The simplest thing you can do that would "solve" this problem would be to keep a FORCEd target that contains a list of the source files represented in the archive, include that file as a prerequisite of libtest.a and compare the contents of the file against the contents of the archive and rebuild/add/delete/etc. to/from the library as appropriate.

libtest.lst: FORCE $(SRC_FILES:.c=.o)
        printf '%s\\n' $(filter-out $<,$^) > $@

libtest.a: libtest.lst $(SRC_FILES:.c=.o)
        ar t $@ > $@.contents
        if diff -q $@.contents libtest.lst; then \
            ar ....; \
        fi
        rm $@.contents

Or if you don't care about avoiding the rebuilding forget the listing/diffing/etc. and just re-run the ar command to build the archive.

As an additional improvement you could add the diffing logic to the libtest.lst recipe instead so that it only updates the libtest.lst file if it changes (to avoid make thinking it needs to run the libtest.a rule when the library contents haven't changed). Something like this.

libtest.lst: FORCE $(SRC_FILES:.c=.o)
        printf '%s\\n' $(filter-out $<,$^) | sort > $@.tmp
        cmp -s $@ $@.tmp || mv $@.tmp $@
        rm -f $@.tmp

libtest.a: libtest.lst $(SRC_FILES:.c=.o)
        ar -rcs $@ $(filter-out $<,$?)
Etan Reisner
  • 77,877
  • 8
  • 106
  • 148
  • 2
    Wouldn't you need to sort both file lists to get a deterministic result? That is: the contents of `libtest.lst` could be sorted when created or, failing that, when used; and the current content of library should be sorted when creating `$@.contents`. Otherwise, newly added/updated files will be at the end of the archive output, but not in the canonical list of files in the archive. – Jonathan Leffler Nov 09 '15 at 17:15
  • @JonathanLeffler Almost certainly. I thought of that as I was writing it and thought about adding a comment to that effect (I wasn't sure I wanted to put it in the code directly for fear of it complicating things and because ultimately I think putting that logic in the `libtest.lst` recipe is a better idea). – Etan Reisner Nov 09 '15 at 17:19
  • IMO, the 1st version results in regenerating `.a` on each run, which is rather undesirable. Only your 2nd improved version is desirable. – Maxim Egorushkin Jul 10 '20 at 21:58