Реализация предварительно скомпилированных заголовков Bazel C++
Я написал реализацию предварительно скомпилированных заголовочных файлов MSVC (PCH) для Bazel (2.0) и хотел бы получить некоторые отзывы о ней, так как мне она не нравится.
Чтобы быстро резюмировать, что нужно сделать, чтобы PCH работал в MSVC:
- Скомпилируйте PCH с помощью /Yc и /Fp, чтобы получить (1)
.pch
файл и (2).obj
файл. - Скомпилируйте двоичный файл, используя /Yu on (1) и снова тот же параметр /Fp.
- Свяжите двоичный файл с помощью
.obj
файл (2).
Реализация
Мы определяем правило, которое принимает pchsrc
(для /Yc) иpchhdr
(для /Fp) в качестве аргумента, а также некоторые изcc_*
аргументы правила (чтобы получить определения и включить). Затем мы вызываем компилятор для получения PCH (в основном следуя продемонстрированному здесь подходу). Когда у нас есть PCH, мы распространяем входные данные местоположения и компоновщика через CcInfo, и пользователю необходимо вызватьcc_pch_copts
чтобы получить параметры /Yu и /Fp.
pch.bzl
load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES")
load("@rules_cc//cc:find_cc_toolchain.bzl", "find_cc_toolchain")
def cc_pch_copts(pchheader, pchtarget):
return [
"/Yu\"" + pchheader + "\"",
"/Fp\"$(location :" + pchtarget + ")\""
]
def _cc_pch(ctx):
""" Create a precompiled header """
cc_toolchain = find_cc_toolchain(ctx)
source_file = ctx.file.pchsrc
pch_file = ctx.outputs.pch
pch_obj_file = ctx.outputs.obj
# Obtain the includes of the dependencies
cc_infos = []
for dep in ctx.attr.deps:
if CcInfo in dep:
cc_infos.append(dep[CcInfo])
deps_cc_info = cc_common.merge_cc_infos(cc_infos=cc_infos)
# Flags to create the pch
pch_flags = [
"/Fp" + pch_file.path,
"/Yc" + ctx.attr.pchhdr,
]
# Prepare the compiler
feature_configuration = cc_common.configure_features(
ctx = ctx,
cc_toolchain = cc_toolchain,
requested_features = ctx.features,
unsupported_features = ctx.disabled_features,
)
cc_compiler_path = cc_common.get_tool_for_action(
feature_configuration = feature_configuration,
action_name = ACTION_NAMES.cpp_compile,
)
deps_ctx = deps_cc_info.compilation_context
cc_compile_variables = cc_common.create_compile_variables(
feature_configuration = feature_configuration,
cc_toolchain = cc_toolchain,
user_compile_flags = ctx.fragments.cpp.copts + ctx.fragments.cpp.cxxopts + pch_flags + ctx.attr.copts,
source_file = source_file.path,
output_file = pch_obj_file.path,
preprocessor_defines = depset(deps_ctx.defines.to_list() + deps_ctx.local_defines.to_list() + ctx.attr.defines + ctx.attr.local_defines),
include_directories = deps_ctx.includes,
quote_include_directories = deps_ctx.quote_includes,
system_include_directories = depset(["."] + deps_ctx.system_includes.to_list()),
framework_include_directories = deps_ctx.framework_includes,
)
env = cc_common.get_environment_variables(
feature_configuration = feature_configuration,
action_name = ACTION_NAMES.cpp_compile,
variables = cc_compile_variables,
)
command_line = cc_common.get_memory_inefficient_command_line(
feature_configuration = feature_configuration,
action_name = ACTION_NAMES.cpp_compile,
variables = cc_compile_variables,
)
args = ctx.actions.args()
for cmd in command_line:
if cmd == "/showIncludes":
continue
args.add(cmd)
# Invoke the compiler
ctx.actions.run(
executable = cc_compiler_path,
arguments = [args],
env = env,
inputs = depset(
items = [source_file],
transitive = [cc_toolchain.all_files],
),
outputs = [pch_file, pch_obj_file],
progress_message = "Generating precompiled header {}".format(ctx.attr.pchhdr),
)
return [
DefaultInfo(files = depset(items = [pch_file])),
CcInfo(
compilation_context=cc_common.create_compilation_context(
includes=depset([pch_file.dirname]),
headers=depset([pch_file]),
),
linking_context=cc_common.create_linking_context(
user_link_flags = [pch_obj_file.path]
)
)
]
cc_pch = rule(
implementation = _cc_pch,
attrs = {
"pchsrc": attr.label(allow_single_file=True, mandatory=True),
"pchhdr": attr.string(mandatory=True),
"copts": attr.string_list(),
"local_defines": attr.string_list(),
"defines": attr.string_list(),
"deps": attr.label_list(allow_files = True),
"_cc_toolchain": attr.label(default = Label("@bazel_tools//tools/cpp:current_cc_toolchain")),
},
toolchains = ["@bazel_tools//tools/cpp:toolchain_type"],
fragments = ["cpp"],
outputs = {
"pch": "%{pchsrc}.pch",
"obj": "%{pchsrc}.pch.obj"
},
provides = [CcInfo],
)
Мы бы использовали это:
BUILD.bzl
load(":pch.bzl", "cc_pch", "cc_pch_copts")
load("@rules_cc//cc:defs.bzl", "cc_binary")
def my_cc_binary(name, pchhdr, pchsrc, **kwargs):
pchtarget = name + "_pch"
cc_pch(
name = pchtarget,
pchsrc = pchsrc,
pchhdr = pchhdr,
defines = kwargs.get("defines", []),
deps = kwargs.get("deps", []),
local_defines = kwargs.get("local_defines", []),
copts = kwargs.get("copts", []),
)
kwargs["deps"] = kwargs.get("deps", []) + [":" + pchtarget])
kwargs["copts"] = kwargs.get("copts", []) + cc_pch_copts(pchhdr, pchtarget))
native.cc_binary(name=name, **kwargs)
my_cc_binary(
name = "main",
srcs = ["main.cpp", "common.h", "common.cpp"],
pchsrc = "common.cpp",
pchhdr = "common.h",
)
с проектом, состоящим из:
main.cpp
#include "common.h"
int main() { std::cout << "Hello world!" << std::endl; return 0; }
common.h
#include <iostream>
common.cpp
#include "common.h"
Вопросы
Реализация работает. Тем не менее, мои темы для обсуждения:
- Как лучше всего распространить дополнительные флаги компиляции на зависимые цели? Как я решил это через
cc_pch_copts
кажется довольно взломанным. Я бы предположил, что это связано с определением поставщика, но я не смог найти того, который позволяет мне пересылать флаги (в CcToolChainConfigInfo есть что-то в этом направлении, но это кажется излишним). - Есть ли другой способ получить все флаги компиляции (определяет, включает и т. Д.), Чем тот, который я реализовал выше? Это действительно многословно, и в большинстве случаев оно не охватывает многих угловых случаев. Можно ли было бы сделать что-то вроде компиляции
empty.cpp
файл вcc_pch
Правило для получения провайдера, который дает прямой доступ ко всем флагам?
Примечание. Я знаю о недостатках предварительно скомпилированных заголовков, но это большая база кода, и, к сожалению, не использовать ее.
2 ответа
Возможно, это можно упростить, сгенерировав фиктивный cpp только для запуска генерации файла pch, нет необходимости связывать полученный объект obj. (как в qmake): вы просто определяете имя заголовка precomp, он сгенерирует фиктивный precomp.h.cpp и использует его для запуска генерации файла pch.
В VS/msbuild также можно просто сгенерировать pch из файла precomp.h (но требует изменения источника): - измените тип элемента заголовка на "C/C++ compile" - установите параметр /Yc на this - добавить директиву hdrstop в конец precomp.h, например
#pragma once
#include <windows.h>
#pragma hdrstop("precomp.h")
Спасибо, что поделились своими файлами bzl, я также изучаю это (большая база кода с заголовками precomp).
Насколько я знаю, предварительно скомпилированные заголовки особенно полезны для разработчиков фреймворков, выполняющих множество метапрограммирования шаблонов и имеющих солидную базу кода. Он не предназначен для ускорения компиляции, если вы все еще разрабатываете фреймворк. Это не ускоряет время компиляции, если код плохо спроектирован и все зависимости идут последовательно. Ваши файлы здесь - это только файл конфигурации VC++, фактическое задание еще даже не начато, а предварительно скомпилированные заголовки - это байт-код. По возможности используйте параллельную сборку.
Кроме того, заголовки получаются ОГРОМНЫМИ!