Consulta configurável (cquery)

7.3 · 7.2 · 7.1 · 7.0 · 6.5

cquery é uma variante de query que processa corretamente select() e os efeitos das opções de build no gráfico de build.

Para isso, ele executa os resultados da fase de análise do Bazel, que integra esses efeitos. Por outro lado, query é executado nos resultados da fase de carregamento do Bazel, antes que as opções sejam avaliadas.

Exemplo:

$ cat > tree/BUILD <<EOF
sh_library(
    name = "ash",
    deps = select({
        ":excelsior": [":manna-ash"],
        ":americana": [":white-ash"],
        "//conditions:default": [":common-ash"],
    }),
)
sh_library(name = "manna-ash")
sh_library(name = "white-ash")
sh_library(name = "common-ash")
config_setting(
    name = "excelsior",
    values = {"define": "species=excelsior"},
)
config_setting(
    name = "americana",
    values = {"define": "species=americana"},
)
EOF
# Traditional query: query doesn't know which select() branch you will choose,
# so it conservatively lists all of possible choices, including all used config_settings.
$ bazel query "deps(//tree:ash)" --noimplicit_deps
//tree:americana
//tree:ash
//tree:common-ash
//tree:excelsior
//tree:manna-ash
//tree:white-ash

# cquery: cquery lets you set build options at the command line and chooses
# the exact dependencies that implies (and also the config_setting targets).
$ bazel cquery "deps(//tree:ash)" --define species=excelsior --noimplicit_deps
//tree:ash (9f87702)
//tree:manna-ash (9f87702)
//tree:americana (9f87702)
//tree:excelsior (9f87702)

Cada resultado inclui um identificador exclusivo (9f87702) da configuração com que o destino é criado.

Como cquery é executado no gráfico de destino configurado, ele não tem insights sobre artefatos como ações de build nem acesso a regras [test_suite](/versions/6.1.0/reference/be/general#test_suite), já que elas não são destinos configurados. Para o primeiro, consulte [aquery](/versions/6.1.0/docs/aquery).

Sintaxe básica

Uma chamada cquery simples tem esta aparência:

bazel cquery "function(//target)"

A expressão de consulta "function(//target)" consiste no seguinte:

  • function(...) é a função a ser executada no destino. cquery oferece suporte à maioria das funções de query, além de algumas novas.
  • //target é a expressão enviada para a função. Neste exemplo, a expressão é um destino simples. No entanto, a linguagem de consulta também permite o aninhamento de funções. Consulte o tutorial de consulta para conferir exemplos.

A cquery exige que um destino seja executado nas fases de carregamento e análise. A menos que especificado de outra forma, cquery analisa os destinos listados na expressão de consulta. Consulte --universe_scope para consultar dependências dos destinos de build de nível superior.

Configurações

A linha:

//tree:ash (9f87702)

significa que //tree:ash foi criado em uma configuração com o ID 9f87702. Para a maioria dos destinos, esse é um hash opaco dos valores da opção de build que definem a configuração.

Para conferir o conteúdo completo da configuração, execute:

$ bazel config 9f87702

A configuração do host usa o ID especial (HOST). Os arquivos de origem não gerados, como os comumente encontrados em srcs, usam o ID especial (null) (porque não precisam ser configurados).

9f87702 é um prefixo do ID completo. Isso ocorre porque os IDs completos são hashes SHA-256, que são longos e difíceis de acompanhar. O cquery entende qualquer prefixo válido de um ID completo, semelhante aos hashes curtos do Git. Para ver os IDs completos, execute $ bazel config.

Avaliação do padrão desejado

//foo tem um significado diferente para cquery e query. Isso ocorre porque cquery avalia destinos configurados, e o gráfico de build pode ter várias versões configuradas de //foo.

Para cquery, um padrão de destino na expressão de consulta é avaliado para cada destino configurado com um rótulo que corresponde a esse padrão. A saída é determinística, mas cquery não garante a ordenação além do contrato de ordenação de consulta principal.

Isso produz resultados mais sutis para expressões de consulta do que com query. Por exemplo, o seguinte pode produzir vários resultados:

# Analyzes //foo in the target configuration, but also analyzes
# //genrule_with_foo_as_tool which depends on a host-configured
# //foo. So there are two configured target instances of //foo in
# the build graph.
$ bazel cquery //foo --universe_scope=//foo,//genrule_with_foo_as_tool
//foo (9f87702)
//foo (HOST)

Se você quiser declarar com precisão qual instância consultar, use a função config.

Consulte a documentação de padrão de destino do query para mais informações sobre padrões de destino.

Funções

Do conjunto de funções com suporte de query, cquery oferece suporte a todas, exceto allrdeps, buildfiles, rbuildfiles, siblings, tests e visible. cquery também introduz estas novas funções:

config

expr ::= config(expr, word)

O operador config tenta encontrar o destino configurado para o rótulo indicado pelo primeiro argumento e a configuração especificada pelo segundo argumento.

Os valores válidos para o segundo argumento são target, host, null ou um hash de configuração personalizada. Os hashes podem ser recuperados de $ bazel config ou da saída de uma cquery anterior.

Exemplos:

$ bazel cquery "config(//bar, host)" --universe_scope=//foo
$ bazel cquery "deps(//foo)"
//bar (HOST)
//baz (3732cc8)

$ bazel cquery "config(//baz, 3732cc8)"

Se nem todos os resultados do primeiro argumento puderem ser encontrados na configuração especificada, apenas aqueles que puderem ser encontrados serão retornados. Se nenhum resultado for encontrado na configuração especificada, a consulta falhará.

Opções

Opções de build

O cquery é executado em um build regular do Bazel e, portanto, herda o conjunto de opções disponíveis durante um build.

Como usar opções de cquery

--universe_scope (lista separada por vírgulas)

Muitas vezes, as dependências de destinos configurados passam por transições, o que faz com que a configuração deles seja diferente da dependência. Essa flag permite que você consulte um destino como se ele fosse criado como uma dependência ou uma dependência transitiva de outro destino. Exemplo:

# x/BUILD
genrule(
     name = "my_gen",
     srcs = ["x.in"],
     outs = ["x.cc"],
     cmd = "$(locations :tool) $< >$@",
     tools = [":tool"],
)
cc_library(
    name = "tool",
)

O Genrules configura as ferramentas na configuração do host, de modo que as consultas a seguir produziriam as seguintes saídas:

Consulta Destino criado Saída
bazel cquery "//x:tool" //x:tool //x:tool(targetconfig)
bazel cquery "//x:tool" --universe_scope="//x:my_gen" //x:my_gen //x:tool(hostconfig)

Se essa flag estiver definida, o conteúdo dela será criado. Se não estiver definido, todos os destinos mencionados na expressão de consulta serão criados. O fechamento transitivo das metas criadas é usado como o universo da consulta. De qualquer forma, os destinos a ser criados precisam ser criados no nível superior, ou seja, compatíveis com as opções de nível superior. cquery retorna resultados no fechamento transitivo dessas metas de nível superior.

Mesmo que seja possível criar todos os destinos em uma expressão de consulta no nível superior, pode ser vantajoso não fazer isso. Por exemplo, definir explicitamente --universe_scope pode impedir a criação de destinos várias vezes em configurações que não são importantes para você. Ele também pode ajudar a especificar qual versão de configuração de um alvo você está procurando, já que não é possível especificar isso de outra forma. Defina essa flag se a expressão de consulta for mais complexa do que deps(//foo).

--implicit_deps (booleano, padrão=True)

Definir essa flag como "false" filtra todos os resultados que não são definidos explicitamente no arquivo BUILD e são definidos em outro lugar pelo Bazel. Isso inclui a filtragem de cadeias de ferramentas resolução.

--tool_deps (booleano, padrão=verdadeiro)

Definir essa flag como falsa filtra todos os destinos configurados para os quais o caminho do destino consultado para eles passa por uma transição entre a configuração de destino e as configurações não relacionadas. Se o destino consultado estiver na configuração de destino, a configuração --notool_deps só vai retornar destinos que também estiverem na configuração de destino. Se o destino consultado estiver em uma configuração que não seja de destino, definir --notool_deps retornará apenas destinos também em configurações que não são de destino. Essa configuração geralmente não afeta a filtragem de toolchains resolvidas.

--include_aspects (booleano, padrão=verdadeiro)

Os aspectos podem adicionar outras dependências a um build. Por padrão, cquery não segue aspectos porque eles aumentam o tamanho do gráfico pesquisável, o que usa mais memória. Mas segui-los produz resultados mais precisos.

Se você não se preocupa com o impacto na memória de consultas grandes, ative essa flag por padrão no bazelrc.

Se você fizer uma consulta com os aspectos desativados, poderá ocorrer um problema em que o destino X falha ao criar o destino Y, mas cquery somepath(Y, X) e cquery deps(Y) | grep 'X' não retornam resultados porque a dependência ocorre por um aspecto.

Formatos de saída

Por padrão, a cquery gera resultados em uma lista de pares de rótulos e configurações ordenada por dependência. Há outras opções para mostrar os resultados.

Transições

--transitions=lite
--transitions=full

As transições de configuração são usadas para criar destinos abaixo dos destinos de nível superior em diferentes configurações.

Por exemplo, um destino pode impor uma transição para a configuração do host em todas as dependências no atributo tools. Elas são conhecidas como transições de atributos. As regras também podem impor transições nas próprias configurações, conhecidas como transições de classe de regras. Esse formato de saída gera informações sobre essas transições, como o tipo delas e o efeito que elas têm nas opções de build.

Esse formato de saída é acionado pela flag --transitions, que é definida como NONE por padrão. Ele pode ser definido no modo FULL ou LITE. O modo FULL gera informações sobre transições de classe de regras e de atributos, incluindo uma diferença detalhada das opções antes e depois da transição. O modo LITE exibe as mesmas informações sem a diferença entre as opções.

Saída de mensagem de protocolo

--output=proto

Essa opção faz com que os destinos resultantes sejam impressos em um formulário de buffer de protocolo binário. A definição do buffer de protocolo pode ser encontrada em src/main/protobuf/analysis.proto.

CqueryResult é a mensagem de nível superior que contém os resultados da cquery. Ela tem uma lista de mensagens ConfiguredTarget e uma lista de mensagens Configuration. Cada ConfiguredTarget tem um configuration_id cujo valor é igual ao do campo id da mensagem Configuration correspondente.

--[no]proto:include_configurations

Por padrão, os resultados do cquery retornam informações de configuração como parte de cada destino configurado. Se você quiser omitir essas informações e receber uma saída de proto formatada exatamente como a saída de proto da consulta, defina essa flag como falsa.

Consulte a documentação de saída do .proto da consulta para mais opções relacionadas à saída do .proto.

Saída do gráfico

--output=graph

Essa opção gera a saída como um arquivo .dot compatível com o Graphviz. Consulte a documentação de saída de gráfico do query para mais detalhes. cquery também é compatível com --graph:node_limit e --graph:factored.

Saída de arquivos

--output=files

Essa opção mostra uma lista dos arquivos de saída produzidos por cada destino correspondente à consulta, semelhante à lista mostrada no final de uma invocação bazel build. A saída contém apenas os arquivos anunciados nos grupos de saída solicitados, conforme determinado pela flag --output_groups. Ela inclui arquivos de origem.

Como definir o formato de saída usando o Starlark

--output=starlark

Esse formato de saída chama uma função Starlark para cada destino configurado no resultado da consulta e mostra o valor retornado pela chamada. A sinalização --starlark:file especifica o local de um arquivo Starlark que define uma função chamada format com um único parâmetro, target. Essa função é chamada para cada Target no resultado da consulta. Como alternativa, para maior comodidade, você pode especificar apenas o corpo de uma função declarada como def format(target): return expr usando a flag --starlark:expr.

Dialeto "cquery" do Starlark

O ambiente cquery Starlark é diferente de um arquivo BUILD ou .bzl. Ele inclui todas as constantes e funçõesprincipais do Starlark, além de algumas específicas de cquery descritas abaixo, mas não (por exemplo) glob, native ou rule e não aceita instruções de carregamento.

build_options(target)

build_options(target) retorna um mapa com chaves que são identificadores de opções de build (consulte Configurações) e valores que são valores do Starlark. As opções de build cujos valores não são valores válidos do Starlark são omitidas deste mapa.

Se o destino for um arquivo de entrada, build_options(target) vai retornar None, já que os destinos de arquivos de entrada têm uma configuração nula.

providers(target)

providers(target) retorna um mapa com chaves que são nomes de provedores (por exemplo, "DefaultInfo") e valores que são os valores do Starlark. Os provedores cujos valores não são valores Starlark válidos são omitidos deste mapa.

Exemplos

Mostre uma lista separada por espaços dos nomes de base de todos os arquivos produzidos por //foo:

  bazel cquery //foo --output=starlark \
    --starlark:expr="' '.join([f.basename for f in target.files.to_list()])"

Imprima uma lista separada por espaços dos caminhos de todos os arquivos produzidos por destinos rule em //bar e seus subpacotes:

  bazel cquery 'kind(rule, //bar/...)' --output=starlark \
    --starlark:expr="' '.join([f.path for f in target.files.to_list()])"

Imprime uma lista dos mnemônicos de todas as ações registradas por //foo.

  bazel cquery //foo --output=starlark \
    --starlark:expr="[a.mnemonic for a in target.actions]"

Mostre uma lista de saídas de compilação registradas por um //baz cc_library.

  bazel cquery //baz --output=starlark \
    --starlark:expr="[f.path for f in target.output_groups.compilation_outputs.to_list()]"

Imprime o valor da opção de linha de comando --javacopt ao criar //foo.

  bazel cquery //foo --output=starlark \
    --starlark:expr="build_options(target)['//command_line_option:javacopt']"

Imprima o rótulo de cada destino com exatamente uma saída. Este exemplo usa funções Starlark definidas em um arquivo.

  $ cat example.cquery

  def has_one_output(target):
    return len(target.files.to_list()) == 1

  def format(target):
    if has_one_output(target):
      return target.label
    else:
      return ""

  $ bazel cquery //baz --output=starlark --starlark:file=example.cquery

Imprime o rótulo de cada destino que é estritamente Python 3. Este exemplo usa funções Starlark definidas em um arquivo.

  $ cat example.cquery

  def format(target):
    p = providers(target)
    py_info = p.get("PyInfo")
    if py_info and py_info.has_py3_only_sources:
      return target.label
    else:
      return ""

  $ bazel cquery //baz --output=starlark --starlark:file=example.cquery

Extrair um valor de um provedor definido pelo usuário.

  $ cat some_package/my_rule.bzl

  MyRuleInfo = provider(fields={"color": "the name of a color"})

  def _my_rule_impl(ctx):
      ...
      return [MyRuleInfo(color="red")]

  my_rule = rule(
      implementation = _my_rule_impl,
      attrs = {...},
  )

  $ cat example.cquery

  def format(target):
    p = providers(target)
    my_rule_info = p.get("//some_package:my_rule.bzl%MyRuleInfo'")
    if my_rule_info:
      return my_rule_info.color
    return ""

  $ bazel cquery //baz --output=starlark --starlark:file=example.cquery

cquery x query

cquery e query se complementam e se destacam em diferentes nichos. Considere o seguinte para decidir qual é a melhor opção:

  • O cquery segue ramos específicos de select() para modelar o gráfico exato que você cria. query não sabe qual ramificação o build escolhe, então faz uma aproximação incluindo todas as ramificações.
  • A precisão de cquery requer a criação de mais do gráfico do que query. Especificamente, cquery avalia destinos configurados, enquanto query só avalia destinos. Isso leva mais tempo e usa mais memória.
  • A interpretação de cquery da linguagem de consulta apresenta ambiguidade que query evita. Por exemplo, se "//foo" existir em duas configurações, qual cquery "deps(//foo)" deverá usar? A função [config](#config) pode ajudar com isso.
  • Como uma ferramenta mais recente, o cquery não oferece suporte a determinados casos de uso. Consulte Problemas conhecidos para mais detalhes.

Problemas conhecidos

Todos os destinos que o cquery "cria" precisam ter a mesma configuração.

Antes de avaliar as consultas, cquery aciona uma compilação logo antes do ponto em que as ações de compilação seriam executadas. Os destinos que ele "cria" são selecionados por padrão entre todos os rótulos que aparecem na expressão da consulta. Isso pode ser substituído com --universe_scope. Eles precisam ter a mesma configuração.

Embora elas geralmente compartilhem a configuração "alvo" de nível superior, as regras podem mudar a própria configuração com transições de borda de entrada. É aí que cquery fica aquém.

Solução alternativa: se possível, defina --universe_scope como um escopo mais restritivo. Exemplo:

# This command attempts to build the transitive closures of both //foo and
# //bar. //bar uses an incoming edge transition to change its --cpu flag.
$ bazel cquery 'somepath(//foo, //bar)'
ERROR: Error doing post analysis query: Top-level targets //foo and //bar
have different configurations (top-level targets with different
configurations is not supported)

# This command only builds the transitive closure of //foo, under which
# //bar should exist in the correct configuration.
$ bazel cquery 'somepath(//foo, //bar)' --universe_scope=//foo

Não é compatível com o --output=xml.

Saída não determinista.

O cquery não limpa automaticamente o gráfico de build de comandos anteriores e, portanto, é propenso a coletar resultados de consultas anteriores. Por exemplo, genquery exerce uma transição de host no atributo tools, ou seja, configura as ferramentas na configuração do host.

Confira os efeitos remanescentes dessa transição abaixo.

$ cat > foo/BUILD <<<EOF
genrule(
    name = "my_gen",
    srcs = ["x.in"],
    outs = ["x.cc"],
    cmd = "$(locations :tool) $< >$@",
    tools = [":tool"],
)
cc_library(
    name = "tool",
)
EOF

    $ bazel cquery "//foo:tool"
tool(target_config)

    $ bazel cquery "deps(//foo:my_gen)"
my_gen (target_config)
tool (host_config)
...

    $ bazel cquery "//foo:tool"
tool(host_config)

Solução alternativa: mude qualquer opção de inicialização para forçar a reanálise das metas configuradas. Por exemplo, adicione --test_arg=&lt;whatever&gt; ao comando de build.

Solução de problemas

Padrões de destino recursivos (/...)

Se você encontrar:

$ bazel cquery --universe_scope=//foo:app "somepath(//foo:app, //foo/...)"
ERROR: Error doing post analysis query: Evaluation failed: Unable to load package '[foo]'
because package is not in scope. Check that all target patterns in query expression are within the
--universe_scope of this query.

isso sugere incorretamente que o pacote //foo não está no escopo, mesmo que --universe_scope=//foo:app o inclua. Isso ocorre devido a limitações de design em cquery. Como solução alternativa, inclua explicitamente //foo/... no escopo do universo:

$ bazel cquery --universe_scope=//foo:app,//foo/... "somepath(//foo:app, //foo/...)"

Se isso não funcionar (por exemplo, porque alguns destinos em //foo/... não podem ser criados com as flags de build escolhidas), descompacte manualmente o padrão nos pacotes constituintes com uma consulta de pré-processamento:

# Replace "//foo/..." with a subshell query call (not cquery!) outputting each package, piped into
# a sed call converting "<pkg>" to "//<pkg>:*", piped into a "+"-delimited line merge.
# Output looks like "//foo:*+//foo/bar:*+//foo/baz".
#
$  bazel cquery --universe_scope=//foo:app "somepath(//foo:app, $(bazel query //foo/...
--output=package | sed -e 's/^/\/\//' -e 's/$/:*/' | paste -sd "+" -))"