自动从静态库中删除文件

Automatically remove files from static library

我想知道是否可以设置 makefile 规则以便自动删除库中不再存在的目标文件而无需进行干净的构建。我的 makefile 设置是这样的。

SRC_FILES = a.c b.c c.c

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

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

现在假设我从 SRC_FILES 中删除 c.c,我希望下一个 运行 从存档中删除相应的目标文件。有没有什么方法可以做到这一点而不必 运行 一个干净的构建?先删除存档然后重建它不起作用,因为当库比其所有依赖项更新时永远不会调用该规则。如果没有任何实际更改,我也不想重建库,因此将其设为 .PHONY 也不起作用。

您无法执行任何自动或内置操作。 make 只是不太擅长注意到这种事情。您可以做的最简单的事情 "solve" 这个问题是保留一个 FORCEd 目标,其中包含存档中表示的源文件列表,将该文件作为 [=14= 的先决条件] 并将文件内容与存档内容和 rebuild/add/delete/etc 进行比较。 to/from 适当的图书馆。

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

或者,如果您不关心避免重建,请忘记 listing/diffing/etc。只需重新运行 ar 命令来构建存档。

作为一项额外的改进,您可以将差异逻辑添加到 libtest.lst 配方中,这样它只会在文件发生变化时更新 libtest.lst 文件(以避免 make 认为它需要运行 libtest.a 规则(当库​​内容未更改时)。像这样。

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已经给出了答案。我尝试像 Etan 一样解决它,但使用“更多制造”和“更少 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)

请注意,这使用隐式规则来创建目标文件。

BSD ar 为全局符号 table 创建类似 __.SYMDEF 的成员。 -grep -v用于过滤掉该成员。

更多细节

LibSources = $(wildcard *.c)

将在您的情况下扩展为 a.c b.c c.c 或您拥有的任何扩展名为 .c 的文件。

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

给出库中应该的目标文件列表。例如。 a.o, b.o, c.o.

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

如果库已经存在,这会给出一个归档对象文件的列表。需要过滤掉索引的条目 __.SYMDEF SORTED

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

这给出了区别,即要删除的目标文件列表。并且

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

因此扩展到已删除的源文件列表。

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

如果不需要删除任何内容,它将扩展为一个空字符串,否则为命令 ar d libtest.a ...objects-to-delete...

库的先决条件是:所有对象都是存档的成员,如果源文件被删除,对象也会被删除。如果库被修改,索引会更新:

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

GNU make 支持 archive members 的规则。如果目标文件不是存档的成员,则会添加:

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

如果源文件被删除(或重命名),则存档中相应的目标文件也会被删除

$(SrcRemoves) :
        $(ArDelete)

另一种可靠的方法是使所有目标文件、存档、共享库和可执行文件都依赖于构建它们的 Makefile。在食谱中,您 $(filter-out Makefile,$^) 从编译器、ar 和链接器命令行中过滤掉 Makefile。最适合 non-recursive makefiles.

这样,每当您更改 Makefile 时,它都会自动重建并重新链接。一种理想的方法是仅在编译器选项更改时重建。这是可能的,但需要一些额外的努力,这可能不值得(ninja 自动执行,但由于 ninja 构建文件通常由更高级别的构建系统生成,例如 CMake, 这个功能几乎毫无意义)。

此外,如果您不分发 .a 文件,您可能喜欢使用带有 T ar 选项的 thin 存档以避免将目标文件不必要地复制到存档中。

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.