Инструмент для автоматической компиляции всех директив ifdef/ifndef

Мой проект C использует директивы препроцессора для активации/деактивации некоторых функций. Нет ничего необычного в том, что некоторые из менее распространенных конфигураций больше не компилируются из-за изменения, сделанного несколько дней назад в файле #ifdef.

Мы используем сценарий для компиляции наиболее распространенных конфигураций, но я ищу инструмент, чтобы убедиться, что все скомпилировано (в нашем случае тестирование не является проблемой, мы просто хотим обнаружить как можно скорее, что компиляция не прекращается). Обычно ifdefs / ifndefs независимы, поэтому обычно каждый модуль должен быть скомпилирован только дважды (все символы определены, все не определены). Но иногда ifdef являются вложенными, поэтому эти модули приходится компилировать несколько раз.

Знаете ли вы какой-либо инструмент для поиска всех ifdef / ifndef (также вложенных) и дает, сколько раз модуль должен быть скомпилирован (с набором символов препроцессора, который должен быть определен в каждом), чтобы гарантировать, что каждая строка исходного кода анализируется компилятором?


person Santiago    schedule 06.01.2010    source источник


Ответы (8)


Вот сценарий 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

Тестирование комбинаций их включения и выключения на самом деле не имеет смысла. Одним из быстрых решений является просто скомпилировать все комбинации и быть уверенным, что некоторые из них не сработают. Ваш тест на правильность тогда может заключаться в том, что компиляция всегда завершается неудачно и успешно с одними и теми же комбинациями.

Еще одна вещь, которую следует помнить, это то, что не все комбинации допустимы, потому что многие из них не являются вложенными. Я думаю, что компиляция относительно дешева, но количество комбинаций может очень быстро вырасти, если вы не будете осторожны. Вы можете заставить скрипт анализировать, какие символы находятся в одной и той же управляющей структуре (например, вложенные #ifdefs), но это намного сложнее реализовать, и я этого здесь не делал.

person James Thompson    schedule 07.01.2010
comment
Спасибо! Вы делаете отличное замечание о количестве комбинаций в другом комментарии, и я добавил краткое упоминание об этой идее в ответ. - person James Thompson; 07.01.2010
comment
Я искал такой скрипт, спасибо! Однако, как отмечено в другом комментарии, нас в основном интересуют все комбинации вложенных ifdef. В нашем проекте определено более 50 имен символов (и, возможно, в будущем будут добавлены еще некоторые, а другие будут удалены), поэтому метод подбора всех комбинаций ifdef в нашем случае невозможен. Я изменю сценарий, чтобы найти все независимые ifdef, а затем сгенерировать все комбинации только для вложенных ifdef. Спасибо! - person Santiago; 07.01.2010
comment
@ Джеймс Томпсон, я не знаю Perl, поэтому прошу прощения за глупый вопрос. Когда я выполнил ваш скрипт в исходной папке, я получил ошибку: неверный верхний каталог в C:/Strawberry/perl/lib/File/Find.pm, строка 472. - person ilya; 16.04.2015

Я не знаю ни одного инструмента для того, чтобы делать то, что вы хотите сделать. Но, глядя на вашу проблему, я думаю, что все, что вам нужно, это скрипт, который скомпилирует исходный код со всеми возможными комбинациями символов препроцессора.

Как сказать, если у вас есть ..

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

Вам нужно будет запустить сборку с помощью следующих комбинаций

  1. A и B оба определены
  2. A не определено и B определено (здесь не имеет смысла, но требуется для всего модуля)
  3. A определено, а B не определено
  4. A и B оба не определены

Хотелось бы увидеть какой-нибудь скрипт, который будет grep исходный код и создавать комбинации. Что-то типа

find ./ -name '*.cpp' -exec egrep -h '^#ifdef' {} \; | awk '{ print $2}' | sort | uniq

С заменой *.cpp любыми файлами, которые вы хотите найти.

person CodeRain    schedule 06.01.2010
comment
Это точно правильный способ сделать это, и я реализовал его в своем ответе. - person James Thompson; 07.01.2010
comment
Я согласен. Однако у OP могут быть некоторые интересные проблемы — количество возможных комбинаций может расти очень быстро, и не все они обязательно действительны. Это потребует много времени на компиляцию! - person gavinb; 07.01.2010
comment
Спасибо за скрипт, я только что использовал его, чтобы обнаружить все имена символов, используемые в коде, и он отлично работал. Однако компилировать все комбинации нецелесообразно, потому что, как вы говорите, они растут очень быстро. И это не требуется, за исключением вложенных ifdef, и ваш сценарий оболочки не может фильтровать вложенные. - person Santiago; 07.01.2010

Вы можете использовать unifdef -s, чтобы получить список всех символов препроцессора, используемых в условных выражениях препроцессора. Судя по обсуждению других ответов, это явно не совсем та информация, которая вам нужна, но если вы также используете параметр -d, выходные данные отладки включают уровень вложенности. Должно быть достаточно просто отфильтровать вывод для создания нужных комбинаций символов.

person Tony Finch    schedule 18.01.2010
comment
Тони, большое спасибо, этот замечательный инструмент обязательно найдет вложенные ifdef в наши исходники. Так что эта информация плюс сценарий, предоставленный в другом ответе, - это то, что мне нужно. Еще одна хорошая вещь, которую вы забыли упомянуть, это то, что инструмент unifdef в настоящее время входит в состав некоторых дистрибутивов Linux (и вы можете использовать параметр -d, даже если на странице руководства ничего об этом не упоминается). - person Santiago; 24.01.2010
comment
Я изменил unifdef, добавив параметр -S, чтобы вы могли получить эту информацию, не включая режим отладки. Версия 352 доступна на сайте dotat.at/prog/unifdef. - person Tony Finch; 22.02.2010

Другое решение состоит в том, чтобы скомпилировать все функции и протестировать их конфигурацию во время выполнения. Это отличный «трюк», так как он позволяет отделу маркетинга продавать различные конфигурации и экономит время инженеров, просто устанавливая значения в файле конфигурации.

В противном случае я предлагаю язык сценариев для построения всех конфигураций.

person Thomas Matthews    schedule 07.01.2010

Извините, я не знаю никаких инструментов, которые могли бы вам помочь, но если бы мне пришлось это сделать, я бы использовал простой скрипт, который делает следующее: - Копирует все исходные файлы в другое место, - Добавляет порядковый номер (в комментарии, очевидно) в начале каждой строки (первая строка кода первого файла = 1, не сбрасывать между файлами), - предварительно обработать, используя все предопределенные конфигурации, и проверить, какие строки были включены, а какие нет, - проверить какие строки были включены, а какие отсутствуют.

Не должно занимать более пары дней, чтобы запустить это, используя, например. Перл или Питон. Что требуется, так это файл со строкой, включающей конфигурации. Это должно быть достаточно быстро сделать с помощью этого скрипта. Просто проверьте, какие строки еще не включены в конфигурации, и отредактируйте файл, пока не будут включены все строки. Затем просто запускайте это время от времени, чтобы убедиться, что нет новых путей.

Анализ источника, как вы хотите, был бы гораздо более сложным сценарием, и я не уверен, что это стоит того.

person Makis    schedule 07.01.2010

Вы можете использовать Hudson с матричный проект. (Обратите внимание, что Hudson — это не просто инструмент для тестирования Java, а очень гибкий сервер сборки, который может создавать что угодно, включая проекты C/C++). Если вы настроите матричный проект, вы получите возможность создать одну или несколько осей. Для каждой оси вы можете указать одно или несколько значений. Затем Hudson запустит вашу сборку, используя все возможные комбинации значений переменных. Например, если вы укажете

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

Хадсон составит шесть комбинаций; 32- и 64-битные версии для Windows, Linux и OSX. При сборке «проекта в свободном стиле» (т. е. при запуске внешнего скрипта сборки) конфигурации задаются с помощью переменных среды. в приведенном выше примере «os» и «windows» будут указаны как переменные среды.

Hudson также поддерживает фильтрацию комбинаций, чтобы избежать создания определенных недопустимых комбинаций (например, 64-разрядные версии Windows можно удалить, но все остальные оставить).

(Пост отредактирован, чтобы предоставить более подробную информацию о матричных проектах.)

person JesperE    schedule 06.01.2010
comment
Я взглянул на Hudson и матричный проект и не вижу никакого отношения к среде тестирования Java для проблемы C #ifdef. Пожалуйста, объясните, почему это относится к #ifdef. - person Philip Schlump; 06.01.2010
comment
Извините, я не очень ясно выразился. Уточню в вопросе. - person JesperE; 07.01.2010
comment
Спасибо за подсказку, но моя проблема заключается не в инструменте, используемом для компиляции всех конфигураций (наш простой скрипт достаточно хорош), а в том, чтобы выяснить, какие комбинации символов препроцессора мне нужно построить, чтобы убедиться, что каждая строка кода скомпилирована. - person Santiago; 07.01.2010

Хм, я сначала подумал, что unifdef может быть полезным, но я смотрю дальше, что вы спрашиваете ибо, нет, это не будет никакой немедленной помощи.

person Vatine    schedule 06.01.2010

Может быть Вам нужен grep?

person vitaly.v.ch    schedule 06.01.2010
comment
grep не сможет проанализировать, какие комбинации опций необходимы для достижения полного охвата, не перепробовав все комбинации. - person Tom Duckering; 06.01.2010