Реализация скомпилированных заголовков Bazel C ++ - PullRequest
0 голосов
/ 01 февраля 2020

Я написал реализацию скомпилированных файлов заголовков MSV C (PCH) для Bazel (2.0) и хотел бы получить некоторую обратную связь, поскольку я не доволен ею.

Чтобы быстро вспомнить, что нужно сделать, чтобы PCH работал в MSV C:

  1. Скомпилируйте PCH с / Y c и / Fp для получения файла (1) .pch и файла (2) .obj.
  2. Скомпилируйте двоичный файл, используя / Yu * ​​1017 * on (1), и снова та же опция / Fp .
  3. Связать двоичный файл с помощью файла .obj (2).

Реализация

Мы определяем правило который принимает pchsrc (для / Y c) и pchhdr (для / Fp ) в качестве аргумента, а также некоторые аргументы правила cc_* ( чтобы получить определения и включает в себя). Затем мы вызываем компилятор для получения PCH (в основном, следуя подходу, продемонстрированному здесь ). Когда у нас есть PCH, мы передаем данные о местоположении и компоновщике через CcInfo , и пользователю нужно позвонить cc_pch_copts, чтобы получить / Yu * ​​1041 * и / 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>

общие. cpp

#include "common.h"

Вопросы

Реализация работает . Тем не менее, мои вопросы для обсуждения:

  • Каков наилучший способ распространения дополнительных флагов компиляции для зависимых целей? То, как я решил это через cc_pch_copts, кажется довольно хакерским. Я бы предположил, что это включает в себя определение провайдера, но я не смог найти того, который позволил бы мне перенаправлять флаги ( CcToolChainConfigInfo имеет что-то в этом направлении, но это кажется излишним).
  • Есть еще один способ получить все флаги компиляции (определяет, включает et c.), чем то, что я реализовал выше? Это действительно многословно, и большинство из них не охватывает множество угловых случаев. Можно ли сделать что-то вроде компиляции файла empty.cpp в правиле cc_pch, чтобы получить провайдера, который дает прямой доступ ко всем флагам?

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

Ответы [ 2 ]

0 голосов
/ 07 февраля 2020

Может быть, это можно упростить, создав фиктивную cpp просто для запуска генерации файла pch, нет необходимости связывать полученный объект. (как в qmake): вы просто определяете имя заголовка precomp, он генерирует фиктивный precomp.h. cpp и использует его для запуска генерации pch-файла.

В VS / msbuild также можно просто сгенерировать pch из файла precomp.h (но требует изменения источника): - изменить тип элемента заголовка на «C / C ++ compile» - установить для этого параметр / Y c - добавьте директиву hdrstop в конец файла precomp.h, например

#pragma once
#include <windows.h>
#pragma hdrstop("precomp.h") 

Спасибо, что поделились своими bzl-файлами, я также изучаю это (большая база кода с заголовками precomp).

0 голосов
/ 01 февраля 2020

Из того, что я знаю, скомпилированные заголовки особенно полезны для разработчиков фреймворков, выполняющих большое количество метапрограммирования шаблонов и имеющих солидную базу кода. Он не предназначен для ускорения компиляции, если вы все еще разрабатываете фреймворк. Это не ускоряет время компиляции, если код плохо спроектирован и все зависимости расположены последовательно. Здесь ваши файлы - только файл конфигурации VC ++, фактическое задание еще даже не запущено, а предварительно скомпилированные заголовки - bytecode. По возможности используйте параллельную сборку.

Кроме того, получающиеся заголовки ОГРОМНЫ!

...