【问题标题】:Automatically remove files from static library自动从静态库中删除文件
【发布时间】:2015-11-09 16:55:31
【问题描述】:

我想知道是否可以设置一个 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 也行不通。

【问题讨论】:

  • 重建库非常便宜(基本上,连接目标文件并生成索引)。你确定这是不可接受的吗?
  • 问题是,重建库的规则永远不会被调用,因为库比它的所有对象都更新。假设我去掉 c.c,那么库依赖 a.o 和 b.o,这两个都比库老。
  • 您可以使用rm libtest.a 手动删除库,然后根据需要重新生成它。
  • 我没有要求“手动”

标签: c makefile unix-ar


【解决方案1】:

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

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

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

GNU ar 可以选择创建一个 thin 存档,其中包含符号索引和对存档成员文件原始副本的引用。这对于构建在本地构建树中使用的库很有用,其中可重定位的对象预计将保持可用,并且复制每个对象的内容只会浪费时间和空间。

【讨论】:

    【解决方案2】:

    虽然这是一个老问题,但 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 为全局符号表创建类似__.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)
    

    【讨论】:

      【解决方案3】:

      没有什么可以自动或内置的。 make 只是不太擅长注意到这种事情。你可以做的最简单的“解决”这个问题的事情是保留一个 FORCEd 目标,其中包含存档中表示的源文件列表,将该文件作为libtest.a 的先决条件包括在内,并比较根据存档内容和重建/添加/删除/等的文件。酌情往返图书馆。

      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
      

      或者,如果您不关心避免重建,请忘记列表/差异/等。然后重新运行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 $<,$?)
      

      【讨论】:

      • 您不需要对两个文件列表进行排序以获得确定性结果吗?即:libtest.lst 的内容可以在创建时排序,或者在使用时排序失败;并且创建$@.contents时要对库的当前内容进行排序。否则,新添加/更新的文件将位于存档输出的末尾,但不在存档中的规范文件列表中。
      • @JonathanLeffler 几乎可以肯定。我在写它时想到了这一点,并考虑添加一条评论来达到这种效果(我不确定我是否想直接把它放在代码中,因为害怕它会使事情复杂化,因为最终我认为把这个逻辑放在 @ 987654334@ recipe 是一个更好的主意)。
      • IMO,第一个版本会在每次运行时重新生成 .a,这是相当不可取的。只有您的第二个改进版本是可取的。
      猜你喜欢
      • 2018-12-18
      • 2021-11-13
      • 2015-06-23
      • 2013-06-17
      • 2015-08-12
      • 2013-08-29
      • 2017-07-05
      • 2023-04-09
      • 1970-01-01
      相关资源
      最近更新 更多