自动编译所有ifdef / ifndef指令的工具

6

我的C项目使用预处理指令来激活/停用一些功能。由于几天前在#ifdef中进行了更改,因此通常会发现一些不常见的配置无法再编译。

我们使用脚本来编译最常见的配置,但我正在寻找一种工具来确保所有内容都被编译(在我们的情况下,测试不是问题,我们只想尽早检测到任何停止编译的情况)。通常,ifdefs / ifndefs 是独立的,因此通常每个模块只需要编译两次(所有符号已定义,所有未定义)。但有时候ifdefs是嵌套的,所以这些模块需要编译更多次。

您是否知道任何工具可以搜索所有ifdef / ifndef(包括嵌套的),并提供每个模块需要编译多少次(在每个模块中定义预处理器符号集)以确保编译器分析每一行源代码?

8个回答

4

我不知道有没有适用于您所需的工具。但是,看到您的问题,我认为您只需要一个脚本来使用所有可能的预处理器符号编译源代码。

比如说,如果您有...

#ifdef A 
    CallFnA();
#ifdef B
    CallFnB();
#endif
    CallFnC();
#endif

您需要使用以下组合来触发构建:
  1. 定义了 A 和 B
  2. A 未定义但 B 已定义(在此不会有意义,但整个模块需要)
  3. 定义了 A 但未定义 B
  4. 两者都未定义
希望能看到一些脚本,可以用它来搜索源代码并生成这些组合。类似于: find ./ -name '*.cpp' -exec egrep -h '^#ifdef' {} \; | awk '{ print $2}' | sort | uniq 将 *.cpp 替换为您想要搜索的任何文件即可。

这正是正确的做法,我在我的答案中已经实现了它。 - James Thompson
1
我同意。不过,楼主可能会遇到一些有趣的挑战——可能的组合数量会非常快地增长,并且并不是所有的组合都一定有效。这将需要大量的编译时间! - gavinb
感谢提供这个脚本,我刚刚用它来发现代码中使用的所有符号名称,效果很好。然而,编译所有组合并不实际,因为正如你所说,它们会快速增长。除了嵌套的ifdef之外,也没有必要这样做,而且您的shell脚本无法过滤嵌套的ifdef。 - Santiago

2
这里有一个Perl脚本,它可以粗略地解析#ifdef条目,并组装出特定文件中使用的符号列表。然后,它打印出所有可能的组合,包括该符号开启或关闭的笛卡尔积。这适用于我的C++项目,但可能需要对您的设置进行微调。
#!/usr/bin/perl

use strict;
use warnings;

use File::Find;

my $path = $ENV{PWD};

my $symbol_map = {};
find( make_ifdef_processor( $symbol_map ), $path );

foreach my $fn ( keys %$symbol_map ) {
   my @symbols = @{ $symbol_map->{$fn} };

   my @options;
   foreach my $symbol (@symbols) {
      push @options, [
         "-D$symbol=0",
         "-D$symbol=1"
      ];
   }

   my @combinations = @{ cartesian( @options ) };
   foreach my $combination (@combinations) {
      print "compile $fn with these symbols defined:\n";
      print "\t", join ' ', ( @$combination );
      print "\n";
   }
}

sub make_ifdef_processor {
   my $map_symbols = shift;

   return sub {
      my $fn = $_;

      if ( $fn =~ /svn-base/ ) {
         return;
      }

      open FILE, "<$fn" or die "Error opening file $fn ($!)";
      while ( my $line = <FILE> ) {
         if ( $line =~ /^\/\// ) { # skip C-style comments
            next;
         }

         if ( $line =~ /#ifdef\s+(.*)$/ ) {
            print "matched line $line\n";
            my $symbol = $1;
            push @{ $map_symbols->{$fn} }, $symbol;
         }
      }
   }
}

sub cartesian {
   my $first_set = shift @_;
   my @product = map { [ $_ ] } @$first_set;

   foreach my $set (@_) {
      my @new_product;
      foreach my $s (@$set) {
         foreach my $list (@product) {
            push @new_product, [ @$list, $s ];
         }
      }

      @product = @new_product;
   }

   return \@product;
}

这段代码肯定无法处理C风格的/* */注释,因为我没有有效地解析它们。另一件需要考虑的事情是,测试所有符号组合可能并不是有意义的,你可以将其构建到脚本或测试服务器中。例如,你可能有互斥的符号来指定一个平台:

-DMAC
-DLINUX
-DWINDOWS

测试这些组合开启和关闭的效果并没有实际意义。一种快速解决方案是编译所有组合,并且对于一些将会失败的组合保持警惕。你的正确性测试可以是编译总是失败和成功相同的组合。

另一个需要记住的事情是并非所有组合都有效,因为其中许多并不是嵌套的。我认为编译相对便宜,但是如果您不小心,组合数量可能会迅速增长。您可以使脚本解析哪些符号位于同一控制结构中(例如嵌套的#ifdef),但这更难实现,我在这里没有这样做。


谢谢!你在另一条评论中提到的组合数量问题非常有道理,我已经在答案中简要提及了这个想法。 - James Thompson
我一直在寻找这样的脚本,谢谢!然而,正如另一个评论中所指出的那样,我们主要关注所有嵌套ifdef的组合。我们的项目定义了50多个符号名称(可能在将来会添加更多,同时删除其他符号名称),因此在我们的情况下,编译所有ifdefs的组合的暴力方法是不可能的。我将调整脚本以查找所有独立的ifdefs,然后仅为嵌套ifdefs生成所有组合。谢谢! - Santiago
@James Thompson,很抱歉我不懂Perl,所以问题可能有些愚蠢。当我在源文件夹中执行您的脚本时,出现了错误:C:/Strawberry/perl/lib/File/Find.pm第472行处的无效顶级目录。 - ilya

1

您可以使用unifdef -s获取所有预处理条件中使用的预处理符号列表。根据其他答案周围的讨论,这显然不是您需要的完整信息,但如果您同时使用-d选项,则调试输出将包括嵌套级别。过滤输出以生成所需的符号组合应该相当简单。


Tony,非常感谢您的帮助。这个强大的工具一定能找到我们源代码中的嵌套“#ifdef”。所以这些信息再加上另一个答案提供的脚本就是我需要的。您忘记提到的另一个好处是,unifdef工具目前已经打包在一些Linux发行版中(即使手册中没有提到,您也可以使用“-d”选项)。 - Santiago
我已经调整了unifdef,添加了一个-S选项,因此您可以在不打开调试模式的情况下获取此信息。版本352可从http://dotat.at/prog/unifdef/获得。 - Tony Finch

0

另一个解决方案是编译所有功能,并进行运行时配置测试,这是一个很酷的“技巧”,因为它允许市场销售不同的配置,并通过在配置文件中设置值来节省工程时间。

否则,我建议使用脚本语言构建所有配置。


0

很抱歉,我不知道任何可以帮助你的工具,但如果我要做这个,我会使用一个简单的脚本完成以下操作: - 将所有源文件复制到另一个位置, - 在每行开头添加一个运行号码(显然是在注释中)(第一个文件的第一行代码=1,不要在文件之间重置), - 使用所有预定义的配置进行预处理,并检查哪些行已被包含和哪些未被包含, - 检查哪些行已被包含,哪些行缺失。

只需使用Perl或Python等工具几天时间就能完成。所需的是一个包含配置的文件。使用此脚本应该足够快。仅需检查哪些行未在配置中包含,并编辑文件,直到每行都被包含。然后定期运行此脚本,以确保没有新路径。

像你想要的那样分析源代码将需要一个更复杂的脚本,我不确定这是否值得麻烦。


-2

您可以将Hudson矩阵项目一起使用。(请注意,Hudson不仅是Java测试工具,还是非常灵活的构建服务器,可以构建几乎任何东西,包括C/C++项目)。如果您设置了一个矩阵项目,您将获得创建一个或多个的选项。对于每个轴,您可以指定一个或多个值。然后,Hudson将使用所有可能的变量值组合运行您的构建。例如,如果您指定

os=windows,linux,osx
wordsize=32,64

Hudson将构建六种组合;每个Windows、Linux和OSX都有32位和64位版本。在构建“自由样式项目”(即启动外部构建脚本)时,使用环境变量指定配置。在上面的示例中,“os”和“windows”将被指定为环境变量。
此外,Hudson还支持过滤组合,以避免构建某些无效的组合(例如,可以删除Windows 64位,但保留所有其他组合)。
(编辑帖子以提供有关矩阵项目的更多详细信息。)

我查看了Hudson和矩阵项目,没有看到任何与Java测试框架相关的内容与C #ifdef问题有关。请解释一下为什么这与#ifdef有关。 - Philip Schlump
抱歉,我的表述不够清晰。我会在问题中进行澄清。 - JesperE
谢谢你的建议,但我的问题不在于编译所有配置所使用的工具(我们的简单脚本已经足够好了),而在于发现我必须构建哪些预处理器符号的组合,以确保每一行代码都被编译。 - Santiago

-2
嗯,我最初认为unifdef可能会有所帮助,但进一步了解你的要求后,它并不能立即提供任何帮助。

-3

也许你需要使用grep?


1
grep 无法分析哪些选项组合是必需的,以实现完全覆盖,而不必尝试所有组合。 - Tom Duckering

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接