﻿# Как мы исправили один баг в CMake

В августе 2019 года в CMake появилась долгожданная поддержка предкомпилированных заголовков\. До этого приходилось использовать разные плагины, например, Cotire\. Сразу после выпуска CMake с новым функционалом было ещё несколько доработок\. Но уже осенью мы посчитали, что можно начать использовать эту фичу, и переписали наши скрипты\. Позже мы нашли баг, который формировал неправильные параметры компилятора Clang и мешал запуску анализатора PVS\-Studio\. Баг пришлось исправить самим\.

![0719_CMakePchFix_ru/image1.png](https://import.viva64.com/docx/blog/0719_CMakePchFix_ru/image1.png)

## Что не так?

Проблема была в том, что исходный файл, в котором использовался предкомпилированный заголовок, был некорректно препроцессирован, если использовался компилятор Clang\. На месте header\-файла было пустое место\. Но в GCC и MSVC все работало нормально\.

## Что делает CMake?

Стоит рассказать о том, как CMake формирует предкомпилированный заголовок\. Для каждого target'а \(а начиная с CMake 3\.17 и для каждого типа сборки – Debug, Release, \.\.\.\) создается 2 файла: _cmake\_pch\.hxx_ и _cmake\_pch\.cxx_\. hxx\-файл – это header\-файл, из которого уже будет сформирован предкомпилированный заголовок, а cxx – пустой файл с содержимым:

```cpp
/* Generated by CMake */
```

Нужен он для генерации предкомпилированного заголовка – он используется как исходник при запуске компилятора\.

Во время сборки проекта заголовочный файл включается через передачу флагов компилятора\. Для Clang это:

```cpp
-Xclang -include-pch -Xclang <PCH_FILE>
```

Т\.е\. в команду компиляции передается уже созданный предкомпилированный заголовок\. В итоге команды запуска сохраняются в файле _compile\_commands\.json_, который анализатор использует для проверки проекта\.

## Проблема

Однако на этом моменте имя оригинального header\-файла потеряно\. При запуске препроцессора Clang пытается достать имя заголовочного файла из созданного предкомпилированного заголовка и в итоге использует пустой файл cmake\_pch\.cxx\. Почему? Если взглянуть в [документацию Clang](https://clang.llvm.org/docs/PCHInternals.html), то можно увидеть их способ создания предкомпилированных заголовков: 

```cpp
To generate PCH files using clang -cc1, use the option -emit-pch:
$ clang -cc1 test.h -emit-pch -o test.h.pch
```

Заголовочный файл test\.h используется в качестве исходного\. А вот как делает CMake:

```cpp
-Xclang -emit-pch -Xclang -include -Xclang <PCH_HEADER>
```

Он включает его через специальный флаг, а затем передает cmake\_pch\.cxx как исходник\. Информация о включенных файлах, переданных через командную строку, к сожалению, теряется, а информация об исходном файле остается\. Это можно узнать, посмотрев [документацию Clang](https://clang.llvm.org/docs/PCHInternals.html):

```cpp
Original file name
    The full path of the header that was used to generate the AST file.
```

## Но как в этом случае работал Cotire?

Cotire по своей структуре похож на CMake, однако cxx\-файл выглядел примерно так:

```cpp
#ifdef __cplusplus
#include "/path/to/header.hxx"
#endif
```

В исходнике у него была директива include, поэтому препроцессор работал нормально\. Это первое, что мы хотели предложить в качестве исправления\.

Однако при таком подходе появились проблемы \- нарушалась кодировка файлов \(компилятор может преобразовывать исходный файл в свою кодировку\), могла сломаться работа других компиляторов, а в CMake 3\.17 файл cmake\_pch\.cxx создавался только один для каждого типа сборки, когда hxx\-файлы могли отличаться\.

## Решение

В итоге было решено передавать в строку компиляции еще и оригинальный исходный файл:

```cpp
-Xclang -include-pch -Xclang <PCH_FILE> -Xclang -include -Xclang <PCH_HEADER>
```

Благодаря опции _\-Xclang_ следующий флаг передается напрямую в препроцессор, минуя драйвер, который [выполняет поиск](https://github.com/llvm-mirror/clang/blob/5cee329c981f3e365e69fef3b05e43144df8a6e2/lib/Driver/ToolChains/Clang.cpp) соответствующего заголовку \.pch/\.gch файла\. Ссылка на патч: [PCH: Clang: Update PCH usage flags to include original header](https://gitlab.kitware.com/cmake/cmake/-/merge_requests/4360)\.

## Заключение

Наш небольшой патч вошел в [CMake 3\.17](https://cmake.org/download/), исправив проблему\. Надеюсь, было интересно, и вы узнали чуть больше о работе CMake\.

Мы можем рекомендовать использовать эту фичу в реальных проектах\.