【问题标题】:Best practice for dependencies on #defines?#defines 依赖项的最佳实践?
【发布时间】:2011-10-15 08:57:44
【问题描述】:

是否有支持依赖于 C/C++ 预处理器标志(如 -DCOMPILE_WITHOUT_FOO)的最佳实践?这是我的问题:

> setenv COMPILE_WITHOUT_FOO
> make <Make system reads environment, sets -DCOMPILE_WITHOUT_FOO>
  <Compiles nothing, since no source file has changed>

我想做的是让所有依赖#ifdef 语句的文件重新编译:

> setenv COMPILE_WITHOUT_FOO
> make
  g++ FileWithIfdefFoo.cpp

如果 COMPILE_WITHOUT_FOO 的值没有改变,我不想重新编译所有内容。

我有一个原始的 Python 脚本(见下文),它基本上编写了一个头文件 FooDefines.h,然后对其进行比较以查看是否有任何不同。如果是,它将替换FooDefines.h,然后传统的源文件依赖项接管。定义在命令行上以-D 传递。缺点是我现在必须在任何使用#ifdef 的源文件中包含FooDefines.h,而且我还有一个新的、动态生成的每个#ifdef 的头文件。如果有工具可以做到这一点,或者有一种方法可以避免使用预处理器,我会全力以赴。

import os, sys
def makeDefineFile(filename, text):
    tmpDefineFile = "/tmp/%s%s"%(os.getenv("USER"),filename) #Use os.tempnam?
    existingDefineFile = filename

    output = open(tmpDefineFile,'w')
    output.write(text)
    output.close()

    status = os.system("diff -q %s %s"%(tmpDefineFile, existingDefineFile))

    def checkStatus(status):
        failed = False
        if os.WIFEXITED(status):
            #Check return code
            returnCode = os.WEXITSTATUS(status)
            failed = returnCode != 0
        else:
            #Caught a signal, coredump, etc.
            failed = True
        return failed,status

    #If we failed for any reason (file didn't exist, different, etc.)
    if checkStatus(status)[0]:
        #Copy our tmp into the new file
        status = os.system("cp %s %s"%(tmpDefineFile, existingDefineFile))
        failed,status  = checkStatus(status)
        print failed, status
        if failed:
            print "ERROR: Could not update define in makeDefine.py"
            sys.exit(status)

【问题讨论】:

    标签: c++ c c-preprocessor conditional-compilation


    【解决方案1】:

    这当然不是最好的方法,但它会起作用:

    find . -name '*cpp' -o -name '*h' -exec grep -l COMPILE_WITHOUT_FOO {} \; | xargs touch
    

    这将查看宏 COMPILE_WITHOUT_FOO 的源代码,并“触摸”每个文件,这将更新时间戳。然后当你运行 make 时,这些文件将重新编译。

    如果你安装了ack,你可以简化这个命令:

    ack -l --cpp COMPILE_WITHOUT_FOO | xargs touch
    

    【讨论】:

    • 这是一个有趣的方法。我认为 grep 就足够了,因为依赖关系往往是平坦的,而不是相互依赖。对我来说不幸的是,我有 Perforce,它使文件处于只读状态,除非它们被签出。如果我使用 git,您的解决方案将是完美的。
    • @Walter - 我也使用 Perforce。您应该能够检出整个目录,这将允许您使用它。但是当你去检查你的更改时,请记住先执行“还原未更改的文件”。
    • 顺便说一句,知道 ack 在某些系统上被称为 ack-grep 会有所帮助。
    【解决方案2】:

    我不相信可以自动确定。预处理器指令不会被编译成任何东西。一般来说,如果我依赖于定义,我希望进行完整的重新编译。 DEBUG 是一个熟悉的例子。

    我认为没有正确的方法可以做到这一点。如果你不能以正确的方式做到这一点,那么最愚蠢的方式可能是你最好的选择。文本搜索 COMPILE_WITH_FOO 并以这种方式创建依赖项。我会将其归类为恶作剧,如果您正在编写共享代码,我建议您向您的同事寻求相当大的支持。

    CMake 有一些工具可以使这更容易。您将创建一个自定义目标来执行此操作。不过,您可能会在这里交易问题,维护一个取决于您的交易品种的文件列表。如果它发生变化,您的文本搜索可能会生成该文件。我使用了类似的技术检查是否需要根据 wget 时间戳重建静态数据存储库。

    Cheetah 是另一个可能有用的工具。

    如果是我,我想我会进行全面重建。

    【讨论】:

    • 不幸的是,我认为你是对的。在这个团队中,强制使用机制不是问题(特别是因为我构建了我们的 make 系统 [在 QMake 之上])。但是,没有标准甚至首选的方法来执行此操作。仅在绝对需要时才应使用预处理器的另一个原因。
    • 条件编译总是很乏味。在日常/集成构建中谨慎使用它是我见过的最不压抑的解决方案。需要注意的是,大多数组织并未设置为在私有分支机构上执行此操作。
    【解决方案3】:

    您的问题似乎是为使用autoconfautoheader 而量身定制的,将变量的值写入config.h 文件。如果这不可能,请考虑从文件中读取“-D”指令并将标志写入该文件。

    在任何情况下,您都必须避免仅依赖环境变量的构建。您无法判断环境何时发生变化。明确需要将变量存储在文件中,最干净的方法是通过 autoconf、autoheader 和源和多个构建树;对于编译上下文的每个切换,通过 re-configure-ing 的第二种最干净的方式;第三个最干净的方式是包含所有可变编译器开关的文件,所有依赖于这些开关的对象都依赖于这些开关。

    当您选择实现第三种方式时,请记住不要不必要地更新此文件,例如通过在临时位置构建它并根据差异有条件地复制它,然后制定规则将能够根据标志有条件地重建您的文件。

    【讨论】:

      【解决方案4】:

      一种方法是将每个#define 的前一个值存储在一个文件中,并在您的makefile 中使用条件来强制更新该文件,只要当前值与前一个值不匹配。任何依赖于该宏的文件都将包含该文件作为依赖项。

      这是一个例子。如果file.c 更改或变量COMPILE_WITHOUT_FOO 与上次不同,它将更新file.o。它使用$(shell ) 将当前值与存储在文件envvars/COMPILE_WITHOUT_FOO 中的值进行比较。如果它们不同,则它会为该文件创建一个依赖于force 的命令,该命令始终会更新。

      file.o: file.c envvars/COMPILE_WITHOUT_FOO
          gcc -DCOMPILE_WITHOUT_FOO=$(COMPILE_WITHOUT_FOO) $< -o $@
      
      ifneq ($(strip $(shell cat envvars/COMPILE_WITHOUT_FOO 2> /dev/null)), $(strip $(COMPILE_WITHOUT_FOO)))
      force: ;
      envvars/COMPILE_WITHOUT_FOO: force
          echo "$(COMPILE_WITHOUT_FOO)" > envvars/COMPILE_WITHOUT_FOO
      endif
      

      如果您想支持未定义宏,则需要使用ifdefifndef 条件,并在文件中指明上次运行时未定义该值。

      【讨论】:

        【解决方案5】:

        Jay 指出“在文件的日期时间戳上触发”。

        理论上,您可以拥有您的主 makefile,称为 m1,包含来自名为 m2 的第二个 makefile 的变量。 m2 将包含所有预处理器标志的列表。

        你可以为你的程序制定一个 make 规则,这取决于 m2 是否是最新的。

        制作 m2 的规则是导入所有环境变量(以及 #include 指令)。

        诀窍是,制作 m2 的规则将检测是否与以前的版本有差异。如果是这样,它将启用一个变量,该变量将强制“全部制作”和/或为主要目标制作干净。否则,它只会更新 m2 上的时间戳,而不会触发完全重制。

        最后,普通目标(make all)的规则将从 m2 获取预处理器指令并根据需要应用它们。

        这在理论上听起来很容易/可能,但实际上 GNU Make 要让这种类型的东西工作起来要困难得多。不过我相信它可以做到。

        【讨论】:

        • 其实很简单。 "include cppflagsfile" 和 "$(ENVDEPOBJS) : cppflagsfile" 已经成功了。
        【解决方案6】:

        在文件的日期时间戳上触发。依赖文件比依赖它的文件更新会触发它重新编译。您必须将每个选项的定义放在单独的 .h 文件中,并确保这些依赖项在 makefile 中表示。然后,如果您更改选项,则依赖于它的文件将自动重新编译。

        如果它考虑到包含文件的包含文件,则您不必更改源的结构。您可以包含一个包含所有单独设置文件的“BuildSettings.h”文件。

        唯一棘手的问题是如果你让它足够聪明来解析include guards。由于包含文件名冲突和包含目录搜索的顺序,我看到了编译问题。

        既然你提到了它,我应该检查一下我的 IDE 是否足够聪明,可以自动为我创建这些依赖项。添加到 IDE 中听起来是一件很棒的事情。

        【讨论】:

        • 为我使用的 IDE 添加了错误报告。运气好的话,他们可能会很快修复它
        猜你喜欢
        • 2019-08-24
        • 2011-08-16
        • 2018-03-03
        • 2011-06-11
        • 2010-12-11
        • 2012-10-09
        • 1970-01-01
        相关资源
        最近更新 更多